Apache HTTP 服务

本文合并自:01-Apache详解.md、02-Apache服务.md、13-Apache-HTTP服务.md、2-1-Apache-Web服务器.md

适用环境:RHEL/CentOS 6(httpd 2.2)与 RHEL/CentOS 7(httpd 2.4)。文中两个版本的写法并列保留,凡有差异之处均标注适用版本,便于在老旧系统与新系统上对照使用。

内容覆盖:HTTP 协议基础、静态与动态网站、Web 服务软件对比、Apache 模块化与三种 MPM 工作模式、yum 与源码两种安装方式、目录结构、httpd.conf 全量常用指令、三类容器与访问控制(2.2 的 Order/Allow/Deny 与 2.4 的 Require)、Basic/Digest 认证、三种虚拟主机、rewrite/deflate/expires/status/ssl 等常用模块、HTTPS、与 PHP 集成、性能调优、日志分析、排错清单与练习。


一、Web 服务基础与 Apache 概述

1.1 HTTP 协议

HTTP(HyperText Transfer Protocol,超文本传输协议)是浏览器与 Web 服务器之间传输网页数据所使用的协议。浏览网页时,绝大多数网站都使用 HTTP 进行数据传输。至于数据在前端如何呈现,属于 HTML 前端开发的范畴,运维人员一般不需要深究;运维主要关注服务端的部署、性能、安全与排障。

HTTP 工作在 TCP 之上,默认端口 80;HTTPS 在 HTTP 与 TCP 之间加入 TLS 层完成加密与身份校验,默认端口 443。

1.1.1 请求报文结构

一个 HTTP 请求由「请求行 + 请求头 + 空行 + 请求体」四部分组成:

GET /index.html HTTP/1.1          <- 请求行:方法 URI 协议版本
Host: www.test.com                <- 请求头(可多行)
User-Agent: curl/7.29.0
Accept: */*
Connection: keep-alive
                                  <- 空行,表示头部结束
name=zhangsan&age=20              <- 请求体(GET 通常没有,POST 才有)

1.1.2 响应报文结构

响应由「状态行 + 响应头 + 空行 + 响应体」组成:

HTTP/1.1 200 OK                   <- 状态行:协议版本 状态码 原因短语
Date: Tue, 01 Nov 2016 13:02:28 GMT
Server: Apache/2.4.7 (Ubuntu)     <- 服务器软件标识
Last-Modified: Tue, 01 Nov 2016 12:10:17 GMT
ETag: "d21e-5403c352b2012"
Accept-Ranges: bytes
Content-Length: 53790
Vary: Accept-Encoding
Cache-Control: max-age=3600
Expires: Tue, 01 Nov 2016 14:02:28 GMT
Content-Type: text/html
                                  <- 空行
<html>...</html>                  <- 响应体

1.1.3 请求方法

方法 说明
GET 请求获取指定资源,参数放在 URL 中,最常用
POST 向服务器提交数据,参数放在请求体中,常用于表单与接口
HEAD 与 GET 相同但只返回响应头、不返回响应体,用于探测资源是否存在(curl -I 即使用该方法)
PUT 上传/替换指定位置的资源,生产环境应禁用
DELETE 删除指定资源,生产环境应禁用
OPTIONS 查询服务器对某资源支持哪些方法
TRACE 回显服务器收到的请求,用于诊断,存在安全风险,建议关闭
CONNECT 建立隧道,常用于 HTTPS 代理

Apache 中可用 <LimitExcept> 限制方法:

<Directory "/www">
    <LimitExcept GET POST HEAD>
        Require all denied
    </LimitExcept>
</Directory>

关闭 TRACE(防跨站追踪攻击):

TraceEnable Off

1.1.4 常见状态码全表

分类 状态码 含义
1xx 信息 100 Continue 客户端应继续发送请求体
101 Switching Protocols 协议切换(如升级为 WebSocket)
2xx 成功 200 OK 请求成功
201 Created 已创建资源
204 No Content 成功但无返回内容
206 Partial Content 部分内容,断点续传/范围请求
3xx 重定向 301 Moved Permanently 永久重定向,SEO 中权重会转移
302 Found 临时重定向
303 See Other 用 GET 访问另一个 URI
304 Not Modified 资源未修改,可用本地缓存(配合 ETag / Last-Modified)
307 Temporary Redirect 临时重定向且保持原方法
4xx 客户端错误 400 Bad Request 请求语法错误
401 Unauthorized 需要 HTTP 认证(Basic/Digest 认证未通过)
403 Forbidden 服务器拒绝访问(权限、Require、SELinux 等)
404 Not Found 资源不存在
405 Method Not Allowed 方法不被允许
408 Request Timeout 请求超时
413 Payload Too Large 请求体过大(如上传超限)
414 URI Too Long URI 过长
416 Range Not Satisfiable 请求范围不合法
499 Client Closed Request 客户端主动断开(Nginx 扩展码,Apache 记为 200/408)
5xx 服务端错误 500 Internal Server Error 服务器内部错误(CGI/PHP 报错最常见)
501 Not Implemented 服务器不支持该功能
502 Bad Gateway 网关/代理从上游收到无效响应(后端 PHP-FPM/Tomcat 挂了)
503 Service Unavailable 服务暂时不可用(超载、维护、连接数打满)
504 Gateway Timeout 网关等待上游响应超时
505 HTTP Version Not Supported 不支持的 HTTP 版本

1.1.5 常用 Header 字段

请求头:

字段 说明
Host 请求的主机名,基于域名的虚拟主机就是靠它区分站点
User-Agent 客户端标识(浏览器/爬虫),日志中的 %{User-Agent}i
Referer 来源页面地址,防盗链靠它判断
Accept 客户端可接受的 MIME 类型
Accept-Encoding 可接受的压缩方式,gzip, deflate,mod_deflate 依赖它
Accept-Language 可接受的语言,内容协商(MultiViews)依赖它
Cookie 客户端携带的 Cookie
Connection keep-alive 表示希望复用连接,close 表示请求后关闭
Range 请求资源的字节范围,断点续传
If-Modified-Since / If-None-Match 条件请求,命中则返回 304
Authorization 认证凭据,Basic 认证即在此携带 base64 编码的用户名密码
X-Forwarded-For 经过代理时记录真实客户端 IP

响应头:

字段 说明
Server 服务器软件标识,由 ServerTokens 控制详细程度
Content-Type 响应体 MIME 类型与字符集,如 text/html; charset=UTF-8
Content-Length 响应体字节数
Content-Encoding 压缩方式,如 gzip
Last-Modified 资源最后修改时间
ETag 资源版本标识
Expires / Cache-Control 缓存控制,mod_expires 生成
Location 重定向目标地址(配合 3xx)
Set-Cookie 下发 Cookie
Vary 缓存需要区分的请求头,如 Accept-Encoding
Strict-Transport-Security HSTS,强制客户端后续使用 HTTPS

1.1.6 长连接 Keep-Alive

HTTP/1.0 默认每个请求都要新建一次 TCP 连接,HTTP/1.1 默认启用长连接(持久连接),一次 TCP 连接可以连续处理多个 HTTP 请求,省去反复三次握手与慢启动的开销。

KeepAlive On                  # 是否开启长连接
MaxKeepAliveRequests 100      # 一次长连接最多处理的请求数(0 表示不限制)
KeepAliveTimeout 15           # 长连接空闲多少秒后关闭
  • KeepAlive On:一次 TCP 连接可处理多次 HTTP 请求,适合一个页面引用大量图片/CSS/JS 或短时间下载文件较多的站点。
  • KeepAlive Off:每个请求都单独建立一次连接。

用 netstat 观察连接情况:

# 统计各 IP 到 80 端口的连接数
netstat -nt | grep :80 | awk -F: '{print $8}' | sort | uniq -c
  • Off 时刷新一个页面,同一 IP 会出现多条连接。
  • On 时同一 IP 只有一条连接,超过 KeepAliveTimeout(默认 15 秒)后再刷新才会产生新连接。
  • 连接空闲约两分钟后会进入 TIME_WAIT 并自动断开。

调优提示:KeepAliveTimeout 设置过大,会让 prefork/worker 模式下的进程/线程长时间被空闲连接占用,生产环境常设为 2~5 秒;event MPM 则由专门线程管理长连接,不存在该问题。

1.2 查看站点使用的 Web 服务器

使用 curl -I 查看响应头中的 Server 字段:

curl -I www.apache.org
HTTP/1.1 200 OK
Date: Tue, 01 Nov 2016 13:02:28 GMT
Server: Apache/2.4.7 (Ubuntu)
Last-Modified: Tue, 01 Nov 2016 12:10:17 GMT
ETag: "d21e-5403c352b2012"
Accept-Ranges: bytes
Content-Length: 53790
Vary: Accept-Encoding
Cache-Control: max-age=3600
Expires: Tue, 01 Nov 2016 14:02:28 GMT
Content-Type: text/html
curl -I www.taobao.com
HTTP/1.1 302 Found
Server: Tengine
Date: Tue, 01 Nov 2016 13:01:54 GMT
Content-Type: text/html
Content-Length: 258
Connection: keep-alive
Location: https://www.taobao.com/
Set-Cookie: thw=cn; Path=/; Domain=.taobao.com; Expires=Wed, 01-Nov-17 13:01:54 GMT;
Strict-Transport-Security: max-age=31536000

其中 Tengine 是阿里在 Nginx 基础上二次开发的 Web 服务器。常见的二次开发版本还有:

缩写 厂商
GWS Google
BWS 百度
SWS 搜狐
Tengine 阿里

它们大多基于 Apache 或 Nginx 二次开发。全球 Web 服务器使用率可参考 survey.netcraft.net 的统计。

1.3 常见 Web 服务软件对比

服务器 定位与说明
Apache(httpd) 开源、模块化、生态成熟、文档完善,模块可插拔,.htaccess 支持目录级配置;处理静态资源与动态模块(mod_php)都行,稳定性好,但高并发下进程/线程模型开销大
Nginx 高并发、轻量、异步事件驱动,静态资源与反向代理/负载均衡能力强,内存占用低;不支持嵌入式 PHP,需通过 FastCGI 交给 php-fpm
Microsoft IIS Windows 平台自带的 Web 服务器,与 ASP/ASP.NET、Windows 域集成紧密
Tomcat Java Servlet/JSP 容器(应用服务器/中间件),用于运行 Java Web 应用,静态资源处理能力弱,一般前置 Nginx 或 Apache
Tengine 阿里巴巴基于 Nginx 二次开发,增强动态模块加载、健康检查等
GWS / BWS / SWS Google、百度、搜狐基于 Apache/Nginx 定制的内部版本

中间件(应用服务器):如果 Web 服务器需要运行 JSP、Servlet 等服务端程序,或支持各种动态特效,还需要中间件支持,常见的有:

  • WebSphere(IBM)
  • Tomcat(Apache 基金会)
  • JBoss(Red Hat)
  • WebLogic(Oracle)

典型分工:Nginx/Apache 处理静态资源并做反向代理,动态请求转给 php-fpm 或 Tomcat 处理。

1.4 静态网站与动态网站

网站分为两类:静态网站和动态网站。判断一个网页是否属于动态网页,重要标志是程序是否在服务器端运行。

类型 运行位置 典型技术 特点
静态网页 客户端 HTML、Flash、JavaScript、VBScript、GIF 动画、图片、.js 实实在在保存在服务器上的文件,每个网页是一个独立文件,服务端直接返回文件、无需解释执行,拷贝到任意位置都能打开
动态网页 服务器端 ASP、PHP、JSP、ASP.net、CGI 随不同客户、不同时间返回不同内容,需要服务器解释执行后生成页面返回,脱离服务端环境无法单独查看
  • 纯粹 HTML 格式的网页通常称为"静态网页",早期网站一般都由静态网页构成。静态网页的网址形式通常为 www.example.com/eg/eg.htm,即以 .htm、.html 等为后缀。
  • HTML 页面上也可以出现 GIF 动画、Flash、滚动字幕等视觉上的"动态效果",但这与动态网页是不同的概念。
  • 动态网页与视觉上的"动态效果"没有直接关系,它可以是纯文字内容,也可以包含动画;无论是否有动态效果,只要采用动态网站技术生成,都称为动态网页。

1.5 Apache 与 httpd 的关系、版本与发展

Apache HTTP Server 是 Apache 软件基金会开发的开源 Web 服务器软件,官网 https://www.apache.org/。

早期的 HTTP Server 叫做 Apache,从 2.0 版本起,官方软件包名与服务名统一为 httpd。因此平时听到的"Apache 服务器"和"httpd 服务"其实指的是同一套软件,本文中的命令与包名一律使用 httpd。

版本 常见发行版 主要特点
httpd 2.2 RHEL/CentOS 6(如 httpd-2.2.15) MPM 编译时确定(prefork/worker 两套二进制),访问控制使用 Order/Allow from/Deny from,配置文件 Include conf.d/*.conf
httpd 2.4 RHEL/CentOS 7(如 httpd-2.4.6) MPM 以动态模块加载可随时切换,新增 event MPM 正式可用,访问控制使用 Require 系列,新增 IncludeOptional、<RequireAll>/<RequireAny>/<RequireNone>,NameVirtualHost 废弃,支持 mod_proxy_fcgi 对接 php-fpm

2.2 与 2.4 的主要差异概览(后文各章节会分别展开):

差异点 httpd 2.2(CentOS 6) httpd 2.4(CentOS 7)
访问控制 Order allow,deny + Allow from / Deny from Require all granted / Require all denied / Require ip / Require host
授权模块 mod_authz_host(老语法) mod_authz_core + mod_authz_host;老语法由 mod_access_compat 兼容
MPM 切换 切换 /usr/sbin/httpd 二进制(RPM)或编译时指定 编辑 /etc/httpd/conf.modules.d/00-mpm.conf 切换 LoadModule
服务管理 service httpd start / /etc/init.d/httpd start + chkconfig httpd on systemctl start httpd + systemctl enable httpd
模块加载配置 全部写在 httpd.conf 中 拆分到 /etc/httpd/conf.modules.d/*.conf
包含子配置 Include conf.d/*.conf IncludeOptional conf.d/*.conf(文件不存在不报错)
名称虚拟主机 需要 NameVirtualHost *:80 自动支持,NameVirtualHost 已废弃
最大并发指令 MaxClients MaxRequestWorkers(MaxClients 作为兼容别名)
子进程请求数上限 MaxRequestsPerChild MaxConnectionsPerChild(旧名兼容)
PID 文件 run/httpd.pid(即 /etc/httpd/run/httpd.pid) /run/httpd/httpd.pid
防火墙 iptables firewalld(firewall-cmd)
PHP 集成 主要用 mod_php mod_php 或 mod_proxy_fcgi + php-fpm

Apache 中文手册:http://apache.chinahtml.com/。安装 httpd-manual 包后可访问本地手册:

# CentOS 7
firefox file:///usr/share/httpd/manual/index.html
# 或通过 HTTP 访问
# http://服务器IP/manual/

# CentOS 6 本地手册目录为 /var/www/manual/

1.6 Apache 的模块化设计(DSO)

Apache 采用核心 + 模块的架构。DSO(Dynamic Shared Object,动态共享对象)即可插拔的功能模块,一个 DSO 就是一个小功能,可按需开关,通过 LoadModule 加载:

LoadModule auth_basic_module     modules/mod_auth_basic.so
LoadModule auth_digest_module    modules/mod_auth_digest.so
LoadModule authn_file_module     modules/mod_authn_file.so
LoadModule authz_host_module     modules/mod_authz_host.so
LoadModule authz_user_module     modules/mod_authz_user.so
LoadModule rewrite_module        modules/mod_rewrite.so
LoadModule deflate_module        modules/mod_deflate.so
LoadModule expires_module        modules/mod_expires.so
LoadModule status_module         modules/mod_status.so
LoadModule ssl_module            modules/mod_ssl.so
  • httpd 2.2:LoadModule 行全部写在 httpd.conf 中。
  • httpd 2.4:拆分到 /etc/httpd/conf.modules.d/ 目录下的多个 .conf 文件中,例如 00-base.conf、00-mpm.conf、00-ssl.conf。

查看模块:

httpd -l      # 查看静态编译进 httpd 的模块(含 MPM)
httpd -M      # 查看当前已加载的所有模块(静态 + 动态共享)

1.7 MPM 工作模式

Apache 通过 MPM(Multi-Processing Module,多处理模块)决定如何处理并发连接,共有 prefork、worker、event 三种。

1.7.1 prefork MPM

非线程、预派生的工作模式。Apache 启动之初就预派生一些子进程等待连接,可以减少频繁创建和销毁进程的开销,也无需担心线程安全问题。但每个进程相对占用资源、消耗大量内存,不擅长处理高并发场景。传统 LAMP 环境一般使用这种模式(因为部分 PHP 扩展不是线程安全的)。

  • 特点:非线程、预派生子进程,一个连接独占一个进程。
  • 优点:线程安全,稳定性好,适合运行 PHP 等不支持线程安全的模块。
  • 缺点:每个连接独占一个进程,内存占用高,高并发场景表现较差。

httpd 2.4 写法:

<IfModule mpm_prefork_module>
    StartServers             5
    MinSpareServers          5
    MaxSpareServers         10
    MaxRequestWorkers      250
    MaxConnectionsPerChild 1000
</IfModule>

httpd 2.2 写法(httpd.conf 中的 prefork.c 段):

<IfModule prefork.c>
StartServers        8      # 启动时产生的子进程数
MinSpareServers     5      # 最小空闲子进程数
MaxSpareServers    20      # 最大空闲子进程数
ServerLimit       256      # 最大进程总数
MaxClients        256      # 最大并发数
MaxRequestsPerChild 4000   # 每个子进程处理的最多请求数
</IfModule>

1.7.2 worker MPM

使用多进程和多线程的混合模式。先预派生一些子进程,子进程再创建多个线程,同时每个子进程包含一个监听线程。线程共享父进程的内存空间,内存占用相对较少;某个线程出现问题只会影响 Apache 的一部分,而不是全部。

但在使用 Keep-Alive 长连接时,某个线程会被一直占用,即使没有请求也要等待超时才会释放。

  • 特点:多进程 + 多线程混合模式。
  • 优点:线程共享进程内存,资源占用比 prefork 低,单机可支撑更高并发。
  • 缺点:Keep-Alive 长连接会占用线程,需等待超时释放;要求所有模块线程安全。
<IfModule mpm_worker_module>
    StartServers             3
    ServerLimit             16
    MinSpareThreads         75
    MaxSpareThreads        250
    ThreadsPerChild         25
    MaxRequestWorkers      400
    MaxConnectionsPerChild 1000
</IfModule>

1.7.3 event MPM

Apache 2.4 推荐的最新工作模式,解决了 Keep-Alive 长连接占用线程资源的问题。它会用一些专门的线程来管理 Keep-Alive 类型的连接,当请求处理结束后允许工作线程立即释放去处理其他请求,从而提升高并发场景下的处理能力。

  • 特点:在 worker 基础上把长连接的等待从工作线程中剥离,交由独立的监听/事件线程管理。
  • 优点:高并发处理能力最强,长连接场景下资源利用率最高。
  • 缺点:httpd 2.2 中还是实验特性,不能与 mod_php 搭配(需改用 php-fpm)。
<IfModule mpm_event_module>
    StartServers             3
    ServerLimit             16
    MinSpareThreads         75
    MaxSpareThreads        250
    ThreadsPerChild         25
    MaxRequestWorkers      400
    MaxConnectionsPerChild 1000
</IfModule>

1.7.4 三种 MPM 对比与参数对照

对比项 prefork worker event
模型 多进程,每进程单线程 多进程 + 每进程多线程 多进程 + 多线程 + 独立事件线程
一个连接占用 一个进程 一个线程 一个线程(长连接空闲时不占工作线程)
内存占用 高 中 中
速度 稍快于 worker 稍慢于 prefork 高并发下最优
稳定性 最高(进程隔离) 一个线程崩溃可能影响整个进程 同 worker
线程安全要求 无 所有模块必须线程安全 所有模块必须线程安全
与 mod_php 支持 不推荐 不支持(应改用 php-fpm)
适用场景 传统 LAMP、模块非线程安全、并发不高 静态资源为主的中高并发 高并发、长连接多、前端接入层
参数 prefork worker / event 说明
StartServers 支持 支持 启动时创建的子进程数
MinSpareServers 支持 — 最小空闲子进程数,不足时创建新进程
MaxSpareServers 支持 — 最大空闲子进程数,超出则回收
MinSpareThreads — 支持 最小空闲线程数(所有进程合计)
MaxSpareThreads — 支持 最大空闲线程数
ThreadsPerChild — 支持 每个子进程创建的线程数
ServerLimit 支持 支持 允许创建的子进程数上限(配置 MaxRequestWorkers 的硬上限)
ThreadLimit — 支持 ThreadsPerChild 的硬上限
MaxRequestWorkers(2.4) / MaxClients(2.2) 支持 支持 最大并发处理请求数
MaxConnectionsPerChild(2.4) / MaxRequestsPerChild(2.2) 支持 支持 每个子进程处理多少请求后自动退出重建,用于规避内存泄漏,0 表示不限制

约束关系:

  • prefork:MaxRequestWorkers ≤ ServerLimit。
  • worker/event:MaxRequestWorkers ≤ ServerLimit × ThreadsPerChild,且 MaxRequestWorkers 必须是 ThreadsPerChild 的整数倍(否则 Apache 会自动上调并告警)。

1.7.5 查看与切换 MPM 模式

查看当前 MPM:

# 查看静态编译进 Apache 的模块(2.2 中 MPM 是静态编译的)
httpd -l
# Compiled in modules:
#   core.c
#   prefork.c        # RPM 版默认运行在 prefork 模式
#   http_core.c
#   mod_so.c

# 查看编译参数与当前生效的 MPM(推荐)
httpd -V

httpd -V 输出中的 Server MPM: 一行即当前使用的 MPM 模式:

Server version: Apache/2.4.6 (CentOS)
Server MPM:     prefork

CentOS 6 / httpd 2.2 切换 MPM:RPM 包中 prefork 和 worker 两个二进制都存在,默认使用 prefork,需要替换二进制文件:

cd /usr/sbin/
mv httpd httpd.prefork
mv httpd.worker httpd
/etc/init.d/httpd restart
httpd -l    # 确认已切换为 worker.c

源码编译版则在编译时用 --with-mpm= 指定,或只保留所需模式的配置段、注释其它模式。

CentOS 7 / httpd 2.4 切换 MPM:MPM 以动态模块方式加载,编辑 /etc/httpd/conf.modules.d/00-mpm.conf,仅保留一行未注释:

LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
#LoadModule mpm_worker_module modules/mod_mpm_worker.so
#LoadModule mpm_event_module modules/mod_mpm_event.so
httpd -t
systemctl restart httpd
httpd -V | grep MPM

注意:

  • 同一时刻只能启用一种 MPM 模块,否则 httpd 无法启动。
  • httpd 2.2 中如需切换工作模式,源码安装需要在编译时指定(--with-mpm=worker)。
  • 修改后用 httpd -V 确认当前生效的 MPM。
  • 参考链接:https://blog.csdn.net/STFPHP/article/details/52954303

二、安装与目录结构

2.1 yum 安装 httpd

# 简单粗暴的全装法(会连带安装 devel、manual 等所有相关包)
yum install httpd* -y

# 推荐的按需安装(CentOS 7)
yum install httpd httpd-tools httpd-manual mod_ssl -y

# CentOS 7 中如需 sudo
sudo yum install httpd httpd-tools httpd-manual -y
包名 说明
httpd 主程序包
httpd-tools 工具包,含 htpasswd、ab、htdigest、rotatelogs 等
httpd-manual 官方手册,安装后可直接访问 http://IP/manual 查看本地文档
httpd-devel 开发包,含 apxs,编译第三方模块时需要
mod_ssl HTTPS 支持模块,见第五章

验证安装:

rpm -qa | grep httpd

CentOS 6(httpd 2.2)典型输出:

httpd-2.2.15-47.el6.centos.4.x86_64
httpd-devel-2.2.15-47.el6.centos.4.x86_64
httpd-manual-2.2.15-47.el6.centos.4.noarch
httpd-tools-2.2.15-47.el6.centos.4.x86_64

CentOS 7(httpd 2.4)典型输出:

httpd-2.4.6-97.el7.centos.x86_64
httpd-tools-2.4.6-97.el7.centos.x86_64
httpd-manual-2.4.6-97.el7.centos.noarch
httpd-devel-2.4.6-97.el7.centos.x86_64

2.2 源码编译安装 httpd

源码安装的好处是可以自定义安装路径、编译参数、启用/禁用模块,以及使用比发行版更新的版本。下载地址:https://httpd.apache.org/download.cgi。

第一步:安装编译环境与依赖

httpd 2.4 依赖 APR(Apache Portable Runtime)、APR-util 和 PCRE:

yum groupinstall "Development Tools" -y
yum install gcc gcc-c++ make wget openssl-devel pcre-devel expat-devel libtool -y

# 方式一:直接用 yum 装依赖(版本满足要求时最省事)
yum install apr apr-devel apr-util apr-util-devel pcre pcre-devel -y

第二步(方式二):源码编译 apr / apr-util / pcre

CentOS 6 自带 apr 版本过低(httpd 2.4 要求 apr ≥ 1.4、apr-util ≥ 1.2),需要自行编译:

cd /usr/src
tar xf apr-1.6.5.tar.gz
cd apr-1.6.5
./configure --prefix=/usr/local/apr
make && make install

cd /usr/src
tar xf apr-util-1.6.1.tar.gz
cd apr-util-1.6.1
./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr
make && make install

cd /usr/src
tar xf pcre-8.44.tar.gz
cd pcre-8.44
./configure --prefix=/usr/local/pcre
make && make install

也可以把 apr、apr-util 源码解压后改名放进 httpd 源码目录的 srclib/apr 与 srclib/apr-util,再给 configure 加 --with-included-apr,一次性编译。

第三步:编译安装 httpd

cd /usr/src
tar xf httpd-2.4.54.tar.gz
cd httpd-2.4.54

./configure \
    --prefix=/usr/local/apache2 \
    --sysconfdir=/etc/httpd24 \
    --enable-so \
    --enable-rewrite \
    --enable-ssl \
    --enable-deflate \
    --enable-expires \
    --enable-headers \
    --enable-modules=most \
    --enable-mpms-shared=all \
    --with-mpm=event \
    --with-apr=/usr/local/apr \
    --with-apr-util=/usr/local/apr-util \
    --with-pcre=/usr/local/pcre \
    --with-zlib

make -j$(nproc)
make install

常用 configure 参数:

参数 说明
--prefix=/usr/local/apache2 安装目录(ServerRoot)
--sysconfdir=/etc/httpd24 配置文件目录,不指定则在 $prefix/conf
--enable-so 启用 DSO 支持,允许以模块方式动态加载功能(必开)
--enable-rewrite 启用地址重写模块 mod_rewrite
--enable-ssl 启用 mod_ssl,支持 HTTPS(需 openssl-devel)
--enable-deflate 启用压缩模块 mod_deflate
--enable-expires 启用缓存过期控制模块 mod_expires
--enable-headers 启用响应头操作模块 mod_headers
--enable-modules=most 编译大部分常用模块
--enable-mods-shared=most 把常用模块编译成共享模块(可 LoadModule 动态加载)
--enable-mpms-shared=all 三种 MPM 都编译成共享模块,可随时切换(2.4 特性)
--with-mpm=prefork\|worker\|event 指定默认使用的 MPM。2.2 中 MPM 是静态编译,只能在此指定,切换必须重新编译
--with-apr= / --with-apr-util= 指定 apr / apr-util 安装路径
--with-pcre= 指定 pcre 路径
--with-zlib 启用 zlib(压缩依赖)
--with-included-apr 使用源码 srclib 内附带的 apr
--disable-userdir 禁用用户个人主页目录

httpd 2.2 的编译示例(MPM 只能编译时定死):

./configure --prefix=/usr/local/apache2 \
    --enable-so --enable-rewrite --enable-ssl \
    --with-mpm=prefork
make && make install

第四步:把源码安装的 httpd 纳入 PATH

echo 'export PATH=/usr/local/apache2/bin:$PATH' > /etc/profile.d/httpd.sh
source /etc/profile.d/httpd.sh

# 验证
which httpd
httpd -v

头文件与库(供编译其他模块使用):

echo '/usr/local/apache2/lib' > /etc/ld.so.conf.d/httpd.conf
ldconfig
ln -sv /usr/local/apache2/include /usr/include/httpd

第五步:纳入 systemd 管理(CentOS 7)

vim /usr/lib/systemd/system/httpd24.service
[Unit]
Description=Apache HTTP Server (compiled from source)
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/usr/local/apache2/logs/httpd.pid
ExecStart=/usr/local/apache2/bin/apachectl start
ExecReload=/usr/local/apache2/bin/apachectl graceful
ExecStop=/usr/local/apache2/bin/apachectl stop
KillSignal=SIGCONT
PrivateTmp=true

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl start httpd24
systemctl enable httpd24

纳入 SysV 管理(CentOS 6):把 apachectl 复制成启动脚本并加上 chkconfig 头。

cp /usr/local/apache2/bin/apachectl /etc/init.d/httpd24
vim /etc/init.d/httpd24

在文件第二行插入:

#!/bin/bash
# chkconfig: 35 85 15
# description: Apache HTTP Server compiled from source
chmod +x /etc/init.d/httpd24
chkconfig --add httpd24
chkconfig httpd24 on
service httpd24 start

2.3 两种安装方式的目录结构对照

内容 yum/RPM 安装(CentOS 6 httpd 2.2) yum/RPM 安装(CentOS 7 httpd 2.4) 源码安装(--prefix=/usr/local/apache2)
ServerRoot /etc/httpd /etc/httpd /usr/local/apache2
主配置文件 /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf /usr/local/apache2/conf/httpd.conf
子配置目录 /etc/httpd/conf.d/ /etc/httpd/conf.d/ /usr/local/apache2/conf/extra/
模块加载配置 写在 httpd.conf 中 /etc/httpd/conf.modules.d/ 写在 httpd.conf 中
文档根目录 /var/www/html/ /var/www/html/ /usr/local/apache2/htdocs/
CGI 目录 /var/www/cgi-bin/ /var/www/cgi-bin/ /usr/local/apache2/cgi-bin/
日志目录 /var/log/httpd/ /var/log/httpd/ /usr/local/apache2/logs/
模块目录 /usr/lib64/httpd/modules/ /etc/httpd/modules/(软链到 /usr/lib64/httpd/modules) /usr/local/apache2/modules/
主程序 /usr/sbin/httpd(另有 httpd.worker) /usr/sbin/httpd /usr/local/apache2/bin/httpd
apachectl /usr/sbin/apachectl /usr/sbin/apachectl /usr/local/apache2/bin/apachectl
PID 文件 /etc/httpd/run/httpd.pid /run/httpd/httpd.pid /usr/local/apache2/logs/httpd.pid
本地手册 /var/www/manual/ /usr/share/httpd/manual/ 需单独下载 httpd-docs
服务脚本 /etc/init.d/httpd /usr/lib/systemd/system/httpd.service 需自行编写(见 2.2 第五步)
默认测试页配置 /etc/httpd/conf.d/welcome.conf /etc/httpd/conf.d/welcome.conf 无

CentOS 7 RPM 安装的关键配置文件清单:

文件 作用
/etc/httpd/conf/httpd.conf 主配置文件
/etc/httpd/conf.d/*.conf 模块化扩展配置,由主配置 IncludeOptional conf.d/*.conf 加载
/etc/httpd/conf.d/welcome.conf 默认测试页配置
/etc/httpd/conf.d/ssl.conf HTTPS 配置(安装 mod_ssl 后生成)
/etc/httpd/conf.d/autoindex.conf 目录列表样式配置
/etc/httpd/conf.d/userdir.conf 用户个人主页配置
/etc/httpd/conf.modules.d/00-mpm.conf MPM 切换
/etc/httpd/conf.modules.d/00-base.conf 基础模块加载

主配置文件通过 Include conf.d/*.conf(2.4 为 IncludeOptional conf.d/*.conf)把 /etc/httpd/conf.d/ 下的所有 .conf 文件包含进来,因此通常将虚拟主机、SSL 等业务配置写入 conf.d 目录,而不直接改主配置文件。

注意:部分配置在虚拟主机中不能生效(例如 Listen、User、Group、MPM 参数、ServerTokens),应当在全局配置中设置。

2.4 服务启停与开机自启

CentOS 6 / RHEL 6(SysV):

/etc/init.d/httpd start          # 启动
/etc/init.d/httpd stop           # 停止
/etc/init.d/httpd restart        # 重启
/etc/init.d/httpd reload         # 重新加载配置(不中断服务)
/etc/init.d/httpd status         # 查看状态
/etc/init.d/httpd configtest     # 语法检查

# 等效的 service 写法
service httpd start
service httpd stop
service httpd restart
service httpd reload
service httpd status

# 开机自启
chkconfig httpd on
chkconfig httpd off
chkconfig --list httpd

CentOS 7 / RHEL 7(systemd):

systemctl start httpd            # 启动
systemctl stop httpd             # 停止
systemctl restart httpd          # 重启
systemctl reload httpd           # 重载配置(不中断已有连接)
systemctl status httpd           # 查看状态
systemctl enable httpd           # 开机自启
systemctl disable httpd          # 取消开机自启
systemctl is-enabled httpd       # 查看是否开机自启

# 带 sudo 的写法
sudo systemctl start httpd
sudo systemctl restart httpd
sudo systemctl enable httpd
sudo systemctl status httpd

查看端口监听:

netstat -ntlp
netstat -ntlp | grep httpd
ss -ntlp | grep httpd
ss -ntlp | grep 443

查看服务日志(CentOS 7):

systemctl status httpd -l
journalctl -xeu httpd

2.5 apachectl 与 httpd 命令行参数

apachectl 是 Apache 提供的服务控制脚本,实际上是对 httpd 二进制的封装。

apachectl start          # 启动
apachectl stop           # 立即停止
apachectl restart        # 立即重启(会中断正在处理的请求)
apachectl graceful       # 优雅重启:处理完当前请求后再重启子进程,不中断服务
apachectl graceful-stop  # 优雅停止:等当前请求处理完再退出
apachectl configtest     # 配置语法检查,等同于 httpd -t
apachectl status         # 显示 mod_status 状态页(需要 lynx/elinks 与 mod_status)
apachectl fullstatus     # 显示完整状态页

httpd 常用命令行参数:

参数 说明
httpd -t 配置文件语法检查,输出 Syntax OK 才可重启
httpd -t -D DUMP_VHOSTS 只检查并输出虚拟主机信息
httpd -t -D DUMP_MODULES 只检查并输出已加载模块
httpd -S 查看虚拟主机解析结果与匹配顺序(排查"访问到了错误站点"最有效)
httpd -M 查看当前已加载的所有模块(静态 + 共享)
httpd -l 查看静态编译进 httpd 的模块(2.2 中可据此判断 MPM)
httpd -v 查看版本号与编译时间
httpd -V 查看版本、编译参数、当前生效的 MPM 与各类默认路径
httpd -L 列出所有可用的配置指令及其作用域
httpd -D <name> 定义参数名,配合配置文件中的 <IfDefine> 使用
httpd -f /path/httpd.conf 指定配置文件启动
httpd -d /path/serverroot 指定 ServerRoot
httpd -k start\|stop\|restart\|graceful\|graceful-stop 服务控制
httpd -X 前台单进程调试模式运行
httpd -e <level> 启动时的日志级别

示例:

# 改完配置先检查语法,再重启,避免服务起不来
httpd -t
apachectl configtest

httpd -V
# Server version: Apache/2.4.6 (CentOS)
# Server MPM:     prefork
# Server's Module Magic Number: 20120211:24
#  -D APR_HAS_SENDFILE
#  -D HTTPD_ROOT="/etc/httpd"
#  -D SERVER_CONFIG_FILE="conf/httpd.conf"

httpd -S
# VirtualHost configuration:
# *:80    is a NameVirtualHost
#          default server aaa.test.com (/etc/httpd/conf.d/vhost.conf:1)
#          port 80 namevhost aaa.test.com (/etc/httpd/conf.d/vhost.conf:1)
#          port 80 namevhost bbb.test.com (/etc/httpd/conf.d/vhost.conf:8)

httpd -M | grep -E 'rewrite|ssl|php'

2.6 防火墙与 SELinux

CentOS 6(iptables):进行实验前,可先清空 iptables 规则并关闭 SELinux。

iptables -F
setenforce 0                     # 临时关闭 SELinux
vim /etc/selinux/config          # 将 SELINUX 改为 disabled,重启后永久生效

CentOS 7(firewalld):使用 firewall-cmd 放行 Web 端口,不要用 iptables -F 清空规则。

firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --reload

# 查看已放行的服务
firewall-cmd --list-all

非标准端口(如 8000)需单独放行:

firewall-cmd --add-port=8000/tcp --permanent
firewall-cmd --reload

SELinux 建议保持开启,按需配置上下文与布尔值。仅在临时排障时使用宽容模式:

getenforce
setenforce 0        # 临时宽容模式,排障用,重启后失效

设置安全上下文(更换网站根目录后必做):

# 打上 httpd 可读内容的类型标签(-R 递归)
chcon -t httpd_sys_content_t /www -R

# 需要 httpd 写入的目录(如上传目录)用可读写类型
chcon -t httpd_sys_rw_content_t /www/upload -R

# 按策略恢复默认上下文,可用于修复标签错乱
restorecon -Rv /www

# 查看目录上下文
ls -Zd /www

chcon 的修改会在执行 restorecon 或文件系统重新打标签后丢失。要永久生效,应先写入策略再恢复:

semanage fcontext -a -t httpd_sys_content_t "/www(/.*)?"
restorecon -Rv /www

semanage 由 policycoreutils-python 包提供,缺失时执行 yum install policycoreutils-python -y。

常用 SELinux 布尔值:

# 允许 httpd 主动发起网络连接(反向代理、连接远端数据库时必开)
setsebool -P httpd_can_network_connect on

# 允许 httpd 连接数据库
setsebool -P httpd_can_network_connect_db on

# 允许 httpd 访问用户家目录
setsebool -P httpd_enable_homedirs on

# 查看所有 httpd 相关布尔值
getsebool -a | grep httpd

-P 表示写入持久化配置,重启后仍生效;不加 -P 只在本次运行期间有效。

非默认端口需加入 SELinux 允许的 http 端口集合:

semanage port -a -t http_port_t -p tcp 8000
semanage port -l | grep http_port_t

2.7 启动后的第一次访问

systemctl start httpd            # CentOS 7
/etc/init.d/httpd start          # CentOS 6

浏览器输入服务器 IP 或 127.0.0.1,看到 Apache / RedHat / CentOS 测试欢迎页即表示启动成功。命令行验证:

curl -I http://127.0.0.1
elinks 127.0.0.1                 # 文本浏览器,需 yum install -y elinks*

三、主配置文件 httpd.conf 详解

主配置文件路径:/etc/httpd/conf/httpd.conf(RPM 安装)。

3.1 三段结构

httpd.conf 传统上分为三个部分:

部分 英文标题 说明
第一段 Section 1: Global Environment 全局环境。影响整个 Apache 服务器的参数,如 ServerRoot、Listen、Timeout、KeepAlive、MPM 参数、LoadModule
第二段 Section 2: 'Main' server configuration 主服务器配置。定义默认站点的参数,如 User、Group、ServerAdmin、DocumentRoot、<Directory>、日志
第三段 Section 3: Virtual Hosts 虚拟主机。定义各个 <VirtualHost> 块

主服务器配置段中的指令同时作为虚拟主机的默认值:虚拟主机中未显式设置的指令,会继承主服务器段的取值。

3.2 全局与主服务器常用指令一览

CentOS 6 / httpd 2.2 典型片段:

ServerTokens OS
ServerRoot "/etc/httpd"
PidFile run/httpd.pid
Timeout 60
KeepAlive Off
MaxKeepAliveRequests 100
KeepAliveTimeout 15
Listen 80
#Listen 12.34.56.78:80
Include conf.d/*.conf
User apache
Group apache
ServerAdmin root@localhost
#ServerName www.example.com:80
DocumentRoot "/var/www/html"
DirectoryIndex index.html index.html.var
AccessFileName .htaccess
ErrorLog logs/error_log
LogLevel warn
ServerSignature On

CentOS 7 / httpd 2.4 典型片段:

ServerRoot "/etc/httpd"
Listen 80
Include conf.modules.d/*.conf
User apache
Group apache
ServerAdmin root@localhost
#ServerName www.example.com:80
DocumentRoot "/var/www/html"
<Directory />
    AllowOverride none
    Require all denied
</Directory>
<IfModule dir_module>
    DirectoryIndex index.html
</IfModule>
AccessFileName .htaccess
ErrorLog "logs/error_log"
LogLevel warn
EnableSendfile on
IncludeOptional conf.d/*.conf

3.3 指令逐项详解

指令 说明
ServerRoot "/etc/httpd" Apache 的基础目录,配置文件、日志、模块等相对路径都以此为起点。源码安装时为 --prefix 指定的目录
PidFile run/httpd.pid 保存 httpd 主进程 PID 的文件,相对于 ServerRoot。2.2 为 run/httpd.pid;2.4 中改由 /etc/httpd/conf.d/ 或 systemd 指定为 /run/httpd/httpd.pid
Listen 80 监听的端口,或 IP:端口。可写多行监听多个端口
User apache / Group apache httpd 子进程运行所使用的用户与组身份。主进程以 root 启动以绑定 80 端口,然后降权为该用户运行子进程,是重要的安全机制
ServerAdmin root@localhost 管理员邮箱,服务器出错时可在错误页面中展示给客户端
ServerName www.example.com:80 服务器主机名与端口。未设置时启动会告警 "Could not reliably determine the server's FQDN";虚拟主机中用于匹配请求的 Host 头
DocumentRoot "/var/www/html" 网站根目录(网站家目录),URL 根路径 / 对应的文件系统目录
DirectoryIndex index.html index.php index.html.var 默认主页文件名列表,按书写顺序依次查找,找到第一个即返回;全部不存在则视为访问目录(可能列目录或报 403)
AccessFileName .htaccess 目录级访问控制文件名,默认 .htaccess
ErrorLog logs/error_log 错误日志路径,相对于 ServerRoot(即 /etc/httpd/logs/error_log,通常软链到 /var/log/httpd/)
LogLevel warn 错误日志级别,取值从详细到简略:debug < info < notice < warn < error < crit < alert < emerg。生产用 warn,排障临时调 debug
CustomLog logs/access_log combined 访问日志路径与所用的日志格式昵称
LogFormat "..." combined 定义日志格式并起一个昵称,供 CustomLog 引用
Timeout 60 连接超时时间(秒)。包括接收请求、等待请求体、发送响应等阶段的最长等待时间
KeepAlive On\|Off 是否启用长连接
MaxKeepAliveRequests 100 一个长连接允许处理的最大请求数,0 表示不限制
KeepAliveTimeout 15 长连接在两次请求之间的空闲等待超时(秒)
AddDefaultCharset UTF-8 为未显式声明字符集的响应添加默认字符集。中文站点视网页实际编码可改为 GB2312、GBK、GB18030
DefaultLanguage zh-CN 默认语言(2.2 中约 685 行),设为简体中文
LanguagePriority zh-CN en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-TW 内容协商时的语言优先顺序
Include conf.d/*.conf 包含其他配置文件(2.2 用法)。匹配不到文件时会报错
IncludeOptional conf.d/*.conf 2.4 新增,匹配不到文件时不报错,更安全
ServerTokens Prod 控制响应头 Server 字段暴露的信息量,见 3.4
ServerSignature On\|Off\|EMail 是否在服务器生成的页面(如 403/404 错误页、目录列表)底部显示 Apache 版本签名与管理员邮箱。生产设为 Off
EnableSendfile on 启用内核 sendfile 零拷贝发送静态文件,提升静态资源性能。NFS 等网络文件系统上可能出错,此时需设为 off
EnableMMAP on 启用内存映射读取文件,同样在 NFS 上可能需要关闭
HostnameLookups Off 是否对客户端 IP 做反向 DNS 解析写入日志。开启会显著拖慢性能,务必保持 Off
ErrorDocument 404 /error/404.html 自定义错误页面
Alias /test "/test" URL 路径到文件系统路径的别名映射
TraceEnable Off 关闭 TRACE 方法,防跨站追踪
MaxRanges 200 / MaxRangeOverlaps 限制 Range 请求数量,防 Range DoS(2.4)
LimitRequestBody 10485760 限制请求体大小(字节),用于限制上传大小
LimitRequestFieldSize 8190 限制单个请求头字段大小
LimitRequestLine 8190 限制请求行长度(限制 URL 长度)

3.4 ServerTokens 版本信息控制

控制服务器响应头 Server 字段中返回的版本与操作系统信息级别。CentOS 6 默认配置为 ServerTokens OS;CentOS 7 默认未显式设置(等效 Full),需自行添加。

取值 httpd 2.2 返回示例 httpd 2.4 返回示例
Prod[uctOnly] Apache Apache
Major Apache/2 Apache/2
Minor Apache/2.2 Apache/2.4
Min[imal] Apache/2.2.11 Apache/2.4.6
OS Apache/2.2.11 (Unix) Apache/2.4.6 (CentOS)
Full Apache/2.2.11 (Unix) PHP/5.2.8 Apache/2.4.6 (CentOS) PHP/5.4.16

生产环境建议:

ServerTokens Prod
ServerSignature Off

修改后重启服务,用 curl -I 查看效果:

curl -I http://IP

3.5 Listen 监听端口

Listen 80              # 监听所有地址的 80 端口
Listen 8000            # 同时监听多个端口
Listen 8080

# 绑定到指定的 IP:端口
Listen 192.168.1.70:80 # 只有该 IP 上的 80 端口对外提供服务
Listen 1.1.1.1:80
Listen 2.2.2.1:80
Listen 3.3.3.1:80

Listen 1.1.1.1:80
Listen 2.2.2.1:8080

Listen 443 https       # 2.4 中可指定协议,mod_ssl 安装后自动添加
  • 可以同时绑定多个端口或"IP+端口"组合。
  • Listen 192.168.1.70:80 表示只有通过该 IP 才能访问 Apache。
  • 每个 <VirtualHost IP:端口> 使用的端口都必须先被 Listen 监听,否则不生效。

3.6 容器指令

容器(Section / Container)指令用于把配置限定在某个范围内生效。

容器 匹配对象 说明
<Directory 路径> 文件系统目录及其所有子目录 最常用,路径为绝对路径,支持 *、? 通配符
<DirectoryMatch 正则> 文件系统目录(正则匹配) 如 <DirectoryMatch "^/www/(user1|user2)/">
<Files 文件名> 文件名(不含路径) 可全局使用,也可嵌套在 <Directory> 内;用 ~ 前缀或 <FilesMatch> 支持正则
<FilesMatch 正则> 文件名(正则匹配) 如 <FilesMatch "\.(jpg|png|gif)$">
<Location URL路径> 客户端请求的 URL 路径 针对 URL 而不是文件系统路径,对 Alias、代理路径同样有效
<LocationMatch 正则> URL 路径(正则匹配) 如 <LocationMatch "^/+$">
<VirtualHost IP:端口> 虚拟主机 见第五章
<IfModule 模块名> 仅当指定模块已加载时才生效 如 <IfModule mod_rewrite.c>、<IfModule dir_module>;! 前缀表示未加载时生效
<IfDefine 名称> 仅当启动时用 -D 名称 定义了该参数才生效 用于按需切换配置
<Limit 方法> / <LimitExcept 方法> 限定/排除 HTTP 方法 如 <LimitExcept GET POST>
<RequireAll> / <RequireAny> / <RequireNone> 组合多个 Require 条件(2.4) 见第四章

匹配与合并顺序(后应用者优先级更高,会覆盖先前的设置):

  1. <Directory>(不含正则)—— 按路径从短到长依次应用(父目录先、子目录后)
  2. <DirectoryMatch> 与带正则的 <Directory ~ >
  3. .htaccess(若 AllowOverride 允许,与所在目录的 <Directory> 一同生效,且会覆盖 <Directory> 中允许被覆盖的部分)
  4. <Files> 与 <FilesMatch>
  5. <Location> 与 <LocationMatch>

由于 <Location> 最后应用,因此当 <Directory> 与 <Location> 配置冲突时,<Location> 的设置会覆盖 <Directory>。

示例(在 RHEL 6 上验证的经典冲突场景,最终结果为不能访问):

<Location "/bbs">
    Order allow,deny
    Deny from all          # Location 拒绝
</Location>

<Directory "/www/bbs">
    Order allow,deny
    Allow from all         # Directory 允许
</Directory>
<!-- 最终:http://IP/bbs 不能访问,Location 优先级更高 -->

三类容器的最小示例:

<Directory "/var/www/html">
    Options Indexes
    Require all granted
</Directory>

<Files ~ "\.jpg$">
    Require all denied
</Files>

<Location /server-status>
    Require local
</Location>

其中 ~ 表示后面的字符串是正则表达式。

3.7 日志格式与占位符全表

ErrorLog logs/error_log
LogLevel warn

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

CustomLog logs/access_log combined
占位符 含义
%h 客户端 IP(HostnameLookups On 时为主机名)
%a 客户端 IP(始终为 IP)
%A 服务器本地 IP
%l 远程日志名(identd),通常为 -
%u 远程用户(HTTP 认证通过的用户名),未认证为 -
%t 请求时间,默认格式 [10/Oct/2020:13:55:36 -0700]
%{%Y-%m-%d %H:%M:%S}t 自定义时间格式
%r 完整请求行,如 GET /index.html HTTP/1.1
%m 请求方法
%U 请求的 URL 路径(不含查询字符串)
%q 查询字符串(含 ?,无则为空)
%H 请求协议,如 HTTP/1.1
%s 状态码(内部重定向前的原始状态码)
%>s 最终状态码(推荐使用)
%b 响应体字节数(不含响应头),无内容时为 -
%B 响应体字节数,无内容时为 0
%O 发送的总字节数(含响应头)
%I 接收的总字节数(含请求头,需 mod_logio)
%D 处理请求耗时(微秒),做慢请求分析用
%T 处理请求耗时(秒)
%{ms}T 处理请求耗时(毫秒,2.4.13+)
%p 服务器端口
%P 处理该请求的子进程 PID
%{tid}P 处理该请求的线程 ID
%v 服务器 ServerName(虚拟主机名,多站点合并日志时必带)
%V 依 UseCanonicalName 决定的服务器名
%f 请求对应的文件系统完整路径
%{Referer}i 请求头中的 Referer(来源页),防盗链分析用
%{User-Agent}i 请求头中的 User-Agent(浏览器/客户端标识)
%{Host}i 请求头中的 Host
%{X-Forwarded-For}i 代理传递的真实客户端 IP
%{名称}i 任意请求头
%{名称}o 任意响应头,如 %{Content-Type}o
%{名称}e 环境变量
%{名称}C Cookie 值
%X 响应完成时的连接状态:X 中断、+ 保持长连接、- 关闭

推荐的生产日志格式(带虚拟主机名、耗时与真实 IP):

LogFormat "%v %h %{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\"" prod
CustomLog logs/access_log prod

3.8 Options 各取值

Options 控制某个目录中允许启用哪些服务器特性。

取值 说明
None 不启用任何额外特性,最安全
All 启用除 MultiViews 以外的全部特性(不推荐)
Indexes 当目录下没有 DirectoryIndex 指定的主页文件时,把目录下的文件与子目录以列表形式显示出来(由 mod_autoindex 生成)
-Indexes 显式关闭列目录功能
FollowSymLinks 允许跟随符号链接。可通过软链接把网站根目录以外的目录纳入访问范围,从而扩展网站根目录
SymLinksIfOwnerMatch 仅当符号链接与其目标文件属主相同时才跟随,比 FollowSymLinks 安全
ExecCGI 允许在该目录中执行 CGI 脚本
Includes 允许 SSI(Server Side Includes,服务器端包含),可在 HTML 中嵌入指令甚至执行命令
IncludesNOEXEC 允许 SSI 但禁止 #exec 与 #include 执行 CGI,较安全
MultiViews 允许内容协商。请求 /page 时会自动在目录中寻找 page.html、page.zh.html、page.en.html 等最合适的文件返回

写法说明:

# 绝对写法:完全替换父目录继承的 Options
Options Indexes FollowSymLinks

# 增量写法:在继承基础上增删(带 + / - 号)
Options +FollowSymLinks -Indexes

# 关闭一切
Options None

注意:同一 Options 中不能混用"带符号"与"不带符号"的写法,否则语法检查会报错。

安全建议:生产环境的静态站点通常写 Options -Indexes +FollowSymLinks,防止目录被遍历。

3.9 AllowOverride 与 .htaccess

AllowOverride 决定 .htaccess 文件(文件名由 AccessFileName 定义)中哪些类型的指令可以覆盖主配置。

取值 说明
None 完全禁用 .htaccess,Apache 不会去查找该文件(性能最好)
All 允许 .htaccess 中使用所有可在目录上下文中生效的指令
AuthConfig 只允许认证相关指令:AuthType、AuthName、AuthUserFile、AuthGroupFile、Require 等
Limit 只允许访问控制指令:Order、Allow、Deny(2.2);Require(2.4)
Options[=选项列表] 只允许 Options 与 XBitHack,可用 = 限定允许设置的具体选项
FileInfo 允许文档类型/处理器/重写相关指令:ErrorDocument、AddType、DefaultType、RewriteRule 等
Indexes 允许目录索引相关指令:DirectoryIndex、IndexOptions、FancyIndexing 等
<Directory "/www">
    AllowOverride None          # 生产推荐
</Directory>

<Directory "/www">
    AllowOverride All           # 允许 .htaccess 全权覆盖
</Directory>

<Directory "/www">
    AllowOverride AuthConfig    # 只允许 .htaccess 做认证
</Directory>

性能考量:

  • 只要 AllowOverride 不是 None,Apache 在处理每一个请求时都会沿着请求路径从 DocumentRoot 逐级向下查找 .htaccess 文件(甚至从 / 开始),产生大量 stat() 系统调用,明显增加磁盘 IO 开销。
  • 因此对自己能控制主配置文件的场景,应当把规则直接写进 <Directory> 容器并设置 AllowOverride None;.htaccess 主要用于虚拟主机托管环境等无法修改主配置的场合。
  • .htaccess 的优点是修改后立即生效,无需重启/重载 Apache。

.htaccess 常见用途示例:

# 目录认证
AuthName "please input your username and password"
AuthType Basic
AuthUserFile /etc/httpd/userpasswd
Require valid-user

# 关闭列目录
Options -Indexes

# 自定义错误页
ErrorDocument 404 /404.html

# 伪静态重写
RewriteEngine On
RewriteRule ^article-([0-9]+)\.html$ /article.php?id=$1 [L]

Apache 默认配置中有一个 <Files> 容器,用于保护 .ht* 开头的文件不被客户端下载:

# httpd 2.2
<Files ~ "^\.ht">
    Order allow,deny
    Deny from all
</Files>
# httpd 2.4
<Files ~ "^\.ht">
    Require all denied
</Files>

3.10 基础配置实践

例 1:建立主页文件

主页文件默认不存在,需要手动创建。文件名必须在 DirectoryIndex 列表中(如 index.html、index.php、index.html.var),否则视为不存在。

echo 'main page' > /var/www/html/index.html

浏览器访问 http://127.0.0.1/,看到 main page 即表示成功。

默认测试欢迎页由 /etc/httpd/conf.d/welcome.conf 控制,删除或注释后就不会再显示默认测试页:

rm -rf /etc/httpd/conf.d/welcome.conf

使用 HTML 标签编写更正式的主页:

vim /var/www/html/index.html
<html>
<head>
<title>笔记站点</title>
</head>
<body>
<center><h1>欢迎来我的网站!@_@</h1></center>
</body>
</html>

保存后刷新浏览器即可看到效果。

例 2:修改网站根目录

编辑主配置文件:

vim /etc/httpd/conf/httpd.conf

将 DocumentRoot 和对应的 <Directory> 容器都改为 /www:

httpd 2.2 写法:

DocumentRoot "/www"

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>

httpd 2.4 写法:

DocumentRoot "/www"

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

创建新目录并设置权限,然后重新加载配置:

mkdir /www
chown -R apache:apache /www          # 亦可写作 chown -R apache.apache /www

# CentOS 7
systemctl reload httpd
# CentOS 6
/etc/init.d/httpd reload

echo 'new main page' > /www/index.html
  • reload 相当于重新加载配置,不是完整重启,不中断已有连接。
  • CentOS 6 实验环境中若不关闭 SELinux,更换家目录后可能报"目录不存在"或 403 错误。
  • CentOS 7 中开启 SELinux 时,新目录需要设置正确的安全上下文:
chcon -t httpd_sys_content_t /www -R
# 或永久生效
semanage fcontext -a -t httpd_sys_content_t "/www(/.*)?"
restorecon -Rv /www

例 3:修改主页类型或主页名

DirectoryIndex 限定了主页文件的支持类型。例如改为:

DirectoryIndex index.php index.html.var

含义是:先找网站根目录下的 index.php,不存在再找 index.html.var,都不存在则提示找不到主页。

echo 'php main page' > /www/index.php

再访问即可正常显示。文本浏览器 elinks 可用于命令行测试:

yum install -y elinks*
elinks 10.1.1.45

例 4:修改语言集和字符集

在主配置文件中添加:

DefaultLanguage zh-CN
LanguagePriority zh-CN en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-TW
AddDefaultCharset UTF-8
指令 说明
DefaultLanguage zh-CN 默认语言为简体中文
AddDefaultCharset UTF-8 默认字符编码;中文站点可能需要改为 gb2312 或 gbk,具体取决于网站文件的默认编码

常见可显示中文的字符集:gbk、gb2312、gb18030、utf-8。

扩展:Windows 默认字符集为 gb2312,Linux 默认字符集为 utf-8。

例 5:自定义错误页面

准备一张图片或页面放到指定位置:

ls /var/www/error/404.png
ls /var/www/error/404.jpg

修改主配置文件,指向该路径(URL 路径,相对于 DocumentRoot):

ErrorDocument 404 /error/404.png
ErrorDocument 404 /error/404.jpg
ErrorDocument 403 /error/403.html
ErrorDocument 500 /error/500.html

# 也可以直接返回一段文本或跳转到外部 URL
ErrorDocument 404 "Sorry, page not found."
ErrorDocument 500 http://www.test.com/error.html
systemctl restart httpd            # CentOS 7
/etc/init.d/httpd restart          # CentOS 6

例 6:中文支持与目录列表长文件名

目录列表页面的字符集:在对应 <Directory> 容器中添加 IndexOptions Charset=UTF-8:

<Directory "/var/www/html">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
    IndexOptions Charset=UTF-8
</Directory>

目录列表中文件名不被截断:编辑 /etc/httpd/conf.d/autoindex.conf,找到 FancyIndexing HTMLTable VersionSort 这一段,添加 NameWidth=*:

IndexOptions FancyIndexing HTMLTable VersionSort NameWidth=* Charset=UTF-8

其他常用 IndexOptions:

选项 说明
FancyIndexing 美化的目录列表(带图标、可点击排序)
HTMLTable 用表格排版目录列表
VersionSort 按版本号规则排序文件名中的数字
NameWidth=* 文件名列宽自适应,不截断长文件名
DescriptionWidth=* 描述列宽自适应
Charset=UTF-8 列表页面字符集
FoldersFirst 目录排在文件前面
IgnoreCase 排序时忽略大小写
SuppressHTMLPreamble 使用 HeaderName 指定的头部文件时不输出默认 HTML 头

隐藏不想在列表中显示的文件:

IndexIgnore .htaccess *.bak *~ README HEADER

四、访问控制与认证

4.1 两代访问控制语法对照

版本 提供模块 指令
httpd 2.2(CentOS 6) mod_authz_host、mod_access_compat(2.4 中保留仅为兼容) Order / Allow from / Deny from
httpd 2.4(CentOS 7) mod_authz_core / mod_authz_host Require 系列

重要:CentOS 7 自带 httpd 2.4,一律使用 Require。两套语法虽然都能被解析,但不可混用——同一容器内同时出现 Order 与 Require 时,授权结果由模块加载顺序决定,表现为难以复现的 403 或放行异常。老文档抄来的 Order allow,deny 应整段替换,而不是叠加。

4.1.1 httpd 2.2 语法:Order / Allow / Deny

Order 决定 allow 与 deny 两类规则的检查顺序,规则可理解为类似 CSS 层叠:后检查的一类规则优先级更高,同时决定了"未被任何规则匹配"时的默认动作。

指令 说明
Order allow,deny 先检查 allow 规则,再检查 deny 规则;默认拒绝(未被 allow 明确允许的将被拒绝),deny 规则最终生效
Order deny,allow 先检查 deny 规则,再检查 allow 规则;默认允许(未被 deny 明确拒绝的将被允许),allow 规则最终生效
Allow from all 允许所有
Deny from all 拒绝所有
Allow from 10.0.0.2 允许指定 IP
Allow from 192.168.0.0/24 允许指定网段
Allow from .cluster.com 允许指定域名下所有主机(需 hosts 文件或 DNS 支持)
Deny from 10.0.0.2 拒绝指定 IP/网段/域名

常用组合完整示例:

# 允许所有
Order allow,deny
Allow from all

# 拒绝所有
Order allow,deny
Deny from all

# 允许所有,但拒绝某个 IP
Order allow,deny
Allow from all
Deny from 1.1.1.3

# 拒绝所有,但允许某个 IP
Order deny,allow
Deny from all
Allow from 1.1.1.3

# 先允许所有再拒绝所有 => 最终拒绝所有(deny 在后,覆盖 allow)
Order allow,deny
Allow from all
Deny from all

# 最终拒绝所有
Order allow,deny
Deny from all
Allow from all

# 最终拒绝所有(即使单独 allow 了一个 IP,因为 Order allow,deny 下 deny 后检查且优先)
Order allow,deny
Deny from all
Allow from 10.0.0.2

4.1.2 httpd 2.4 语法:Require

写法 含义
Require all granted 允许所有访问
Require all denied 拒绝所有访问
Require ip 192.168.0.0/24 允许指定网段
Require ip 1.1.1.3 允许指定 IP
Require ip 192.168.0.0/24 10.0.0.5 一行写多个来源
Require host example.com 允许指定域名(需反解,有性能代价)
Require local 只允许本机(127.0.0.1、::1 及服务器自身 IP)
Require valid-user 允许密码文件中的所有认证用户
Require user u1 u2 只允许指定的认证用户
Require group admin 只允许指定认证组的用户(需 AuthGroupFile)
Require not ip 1.1.1.3 排除指定 IP,必须写在 <RequireAll> 或 <RequireNone> 内
Require env 变量名 环境变量存在时允许(防盗链常用)
Require expr "表达式" 按表达式判断(2.4 强大的表达式语法)

访问控制示例:

# 允许所有
Require all granted

# 拒绝所有
Require all denied

# 仅允许指定网段
Require ip 192.168.0.0/24

# 仅允许指定域名
Require host example.com

# 允许所有,但排除指定 IP
<RequireAll>
    Require all granted
    Require not ip 1.1.1.3
</RequireAll>

# 只放行指定 IP,其余全部拒绝(未匹配任何 Require 即被拒绝)
Require ip 1.1.1.3

# 放行多个来源,满足任一条即通过
Require ip 192.168.0.0/24
Require ip 10.0.0.5

# 必须同时来自内网且已通过认证
<RequireAll>
    Require ip 192.168.0.0/24
    Require valid-user
</RequireAll>

# 表达式写法:只允许特定 UA
Require expr "%{HTTP_USER_AGENT} =~ /curl/"

<RequireAny>(默认行为)满足任一条件即通过;<RequireAll> 内所有条件必须同时满足;<RequireNone> 全部不匹配才通过。只有在 <RequireAll> / <RequireNone> 中才能使用 Require not。

4.1.3 等价写法对照表

需求 httpd 2.2 httpd 2.4
允许所有 Order allow,deny
Allow from all
Require all granted
拒绝所有 Order allow,deny
Deny from all
Require all denied
只允许某 IP Order deny,allow
Deny from all
Allow from 1.1.1.3
Require ip 1.1.1.3
允许所有但排除某 IP Order allow,deny
Allow from all
Deny from 1.1.1.3
<RequireAll>
Require all granted
Require not ip 1.1.1.3
</RequireAll>
只允许本机 Order deny,allow
Deny from all
Allow from 127.0.0.1
Require local
只允许某域名 Allow from .cluster.com Require host cluster.com
允许认证用户 Require valid-user Require valid-user(两版本相同)

4.2 Directory 容器示例

httpd 2.2:

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>

httpd 2.4:

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

httpd 2.4 默认对根目录 / 的保护(防止把整个文件系统暴露出去):

<Directory />
    AllowOverride none
    Require all denied
</Directory>

练习:在 /www 下新建 bbs 目录并创建测试文件,使用 Directory 容器控制该子目录仅允许自己的 IP 访问,其它 IP 全部拒绝。

mkdir /www/bbs
echo 'bbs page' > /www/bbs/index.html

httpd 2.2:

<Directory "/www/bbs">
    Order deny,allow
    Deny from all
    Allow from 1.1.1.3
</Directory>

httpd 2.4:

<Directory "/www/bbs">
    Require ip 1.1.1.3
</Directory>

注意:

  • <Directory> 容器不能嵌套,子目录需写绝对路径、单独定义一个容器,不要嵌在 /www 的容器里面。
  • Allow from 后也可以写域名,例如 .cluster.com 表示 cluster.com 域下的所有主机(需要 hosts 文件和 DNS 支持)。

4.3 Files 容器(针对文件控制)

<Files> 容器需要嵌套在 <Directory> 容器内部,才能对指定目录下的文件生效。

httpd 2.2:

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all

    # 控制 /www/1 文件:允许所有,但拒绝 1.1.1.3
    <Files "1">
        Order allow,deny
        Allow from all
        Deny from 1.1.1.3
    </Files>
</Directory>

httpd 2.4:

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted

    <Files "1">
        <RequireAll>
            Require all granted
            Require not ip 1.1.1.3
        </RequireAll>
    </Files>
</Directory>

以上表示 1.1.1.3 不能访问 /www/1。如果要控制 /www/bbs/1,不能写成 <Files "bbs/1">,而需要在 /www/bbs 的 <Directory> 容器里嵌套 <Files> 标签。

两种错误写法:

# 错误:Files 中写绝对路径不生效,必须嵌套到 Directory 内
<Files "/www/1">
    Order allow,deny
    Deny from all
</Files>

# 错误:不在任何 Directory 内,会对整个文档目录(DocumentRoot 及其子目录)生效
<Files "1">
    Order allow,deny
    Deny from all
</Files>

注意:

  • <Files> 中不能写路径(如 <Files "bbs/1">),只能写文件名或正则。
  • <Files> 容器对所在 <Directory> 目录及其子目录生效,对上一级目录没有影响。
  • <Files> 容器如果不在任何 <Directory> 内,将对整个数据目录生效。

正则匹配文件(~ 前缀,或使用 <FilesMatch>):

# 禁止访问所有 jpg 文件
<Files ~ "\.jpg$">
    Require all denied
</Files>

# 禁止访问 jpg 与 png
<Files ~ "\.(jpg|png)$">
    Order deny,allow
    Deny from all
</Files>

# 等价的 FilesMatch 写法(推荐)
<FilesMatch "\.(jpg|png|gif)$">
    Require all denied
</FilesMatch>

# 保护 .ht 开头的文件(默认配置)
<Files ~ "^\.ht">
    Require all denied
</Files>

4.4 Location 容器(对 URL 控制)

<Location> 容器针对客户端请求的 URL 路径进行控制,而不是文件系统路径。

httpd 2.2:

<Location "/bbs">
    Order allow,deny
    Deny from all
</Location>

httpd 2.4:

<Location "/bbs">
    Require all denied
</Location>

<Location /server-status>
    Require local
</Location>

这是对 http://IP/bbs 这种 URL 进行权限控制。

Directory 与 Location 冲突时的处理:如果两者配置冲突(一个允许、一个拒绝),由于 <Location> 在合并顺序中最后应用,<Location> 的优先级更高,最终以 <Location> 为准(在 RHEL 6 上验证结果是拒绝)。示例见 3.6 节。

4.5 基于用户的 Basic 认证

使用用户名和密码确认用户身份(HTTP Basic Auth)。相关指令:

指令 说明
AuthType Basic 认证方式,Basic 表示基本认证(凭据以 base64 编码传输,明文 HTTP 下可被抓包还原,建议配合 HTTPS)
AuthName "提示语" 认证域名称/提示信息,会显示在浏览器登录框中
AuthUserFile /etc/httpd/userpasswd 指定账号密码文件路径
AuthGroupFile /etc/httpd/groupfile 指定组文件路径
Require valid-user 允许账号文件中的所有用户登录
Require user u1 u2 只允许指定用户登录
Require group admin 只允许指定组的用户登录
AuthBasicProvider file 指定认证数据来源(file、dbm、ldap 等),默认 file

4.5.1 htpasswd 命令

htpasswd 由 httpd-tools 包提供,用于创建与管理密码文件。该用户与系统用户无关,是 Apache 独立维护的用户。

# 创建密码文件并添加第一个用户 aaa(-c 表示 create)
htpasswd -c /etc/httpd/userpasswd aaa
# New password:
# Re-type new password:
# Adding password for user aaa

# 文件已存在,追加第二个用户 bbb,不要再加 -c
htpasswd /etc/httpd/userpasswd bbb
# New password:
# Re-type new password:
# Adding password for user bbb

# 非交互方式添加/修改用户(-b 从命令行读密码,注意会留在 history 中)
htpasswd -b /etc/httpd/userpasswd ccc 123456

# 一次性创建文件并非交互添加用户
htpasswd -cb /etc/httpd/userpasswd aaa 123456

# 删除用户
htpasswd -D /etc/httpd/userpasswd bbb

# 校验密码是否正确(2.4.4+)
htpasswd -v /etc/httpd/userpasswd aaa

# 指定加密算法
htpasswd -m /etc/httpd/userpasswd aaa      # MD5(默认)
htpasswd -B /etc/httpd/userpasswd aaa      # bcrypt,最安全
htpasswd -s /etc/httpd/userpasswd aaa      # SHA1
htpasswd -d /etc/httpd/userpasswd aaa      # crypt(仅前 8 位有效,不推荐)
htpasswd -p /etc/httpd/userpasswd aaa      # 明文(不推荐)
选项 说明
-c 创建新密码文件;若文件已存在会被覆盖清空,仅首次使用
-b 从命令行参数读取密码(批量脚本用)
-D 删除指定用户
-v 验证用户密码
-n 不写文件,把结果输出到标准输出
-m / -B / -s / -d / -p 指定密码加密算法

密码文件内容为"用户名:密文":

cat /etc/httpd/userpasswd
aaa:JscuRCtWV0Sh.
bbb:C6wrx4pFco/lk

安全建议:密码文件必须放在 DocumentRoot 之外(否则可被下载),并收紧权限:

chown root:apache /etc/httpd/userpasswd
chmod 640 /etc/httpd/userpasswd

4.5.2 方式一:使用 .htaccess 文件认证

第一步:在要保护的目录下创建 .htaccess 文件。

vim /www/.htaccess
AuthName "please input your username and password! @_@"
AuthType Basic
AuthUserFile /etc/httpd/userpasswd
Require valid-user

指令名不区分大小写,老资料中常写成全小写形式,效果相同:

authname "please input your username and password! @_@"
authtype basic
authuserfile /etc/httpd/userpasswd
require valid-user

第二步:使用 htpasswd 创建密码文件并添加用户(见 4.5.1)。

htpasswd -c /etc/httpd/userpasswd aaa
htpasswd /etc/httpd/userpasswd bbb

第三步:在 httpd.conf 中允许 .htaccess 生效,把对应目录的 AllowOverride 从 None 改为 All(或至少 AuthConfig)。

httpd 2.2:

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride All
    Order allow,deny
    Allow from all
</Directory>

httpd 2.4:

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

第四步:重启服务。

systemctl restart httpd            # CentOS 7
/etc/init.d/httpd restart          # CentOS 6

访问 http://IP 时需要输入用户名和密码。再次访问时浏览器会缓存凭据,需要清空浏览器缓存或换用隐私窗口才会重新要求验证。

命令行验证:

curl -I http://IP/                 # 返回 401 Unauthorized
curl -u aaa:123456 http://IP/      # 带凭据访问

4.5.3 方式二:直接写在容器中

不使用 .htaccess 文件,把认证指令直接写入 <Directory> 容器(推荐,性能更好)。需要控制哪个目录及其子目录,就在 <Directory> 中指定哪个目录。

<Directory "/var/www/html/secret">
    AuthName "Authenticate yourself"
    AuthType basic
    AuthUserFile /etc/httpd/userpasswd
    Require valid-user
</Directory>

httpd 2.2 完整写法:

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all

    authname "please input your username and password! @_@"
    authtype basic
    authuserfile /etc/httpd/userpasswd
    require valid-user
</Directory>

另一种常见写法(配合 AllowOverride AuthConfig):

<Directory "/www">
    AllowOverride AuthConfig
    AuthName "Restricted Files"
    AuthType Basic
    AuthUserFile /etc/httpd/userpasswd
    Require valid-user
</Directory>

然后同样创建密码文件并重启:

htpasswd -c /etc/httpd/userpasswd aaa
systemctl restart httpd

只允许特定用户(而非密码文件中所有人):

<Directory "/www/admin">
    AuthType Basic
    AuthName "Admin Only"
    AuthUserFile /etc/httpd/userpasswd
    Require user aaa
</Directory>

注意:如果一个目录使用了密码控制,那么在通过浏览器列出该目录的父目录时,将看不到该目录(即该目录被隐藏),但仍然可以通过直接输入 URL 进行访问并触发认证。

4.5.4 组认证 AuthGroupFile

先创建组文件,格式为 组名: 用户1 用户2:

vim /etc/httpd/groupfile
admin: aaa bbb
dev: ccc ddd

配置:

<Directory "/www/admin">
    AuthType Basic
    AuthName "Admin Group Only"
    AuthUserFile /etc/httpd/userpasswd
    AuthGroupFile /etc/httpd/groupfile
    Require group admin
</Directory>

组文件中的用户必须同时存在于 AuthUserFile 密码文件中。

4.6 Digest 摘要认证

Basic 认证以 base64 编码传输密码,等同明文;Digest 认证使用 MD5 摘要挑战-响应机制,密码不在网络上明文传输,安全性略高(但仍不如 HTTPS)。

需要加载模块:

LoadModule auth_digest_module modules/mod_auth_digest.so

创建摘要密码文件(用 htdigest,注意需要指定认证域 realm,且必须与 AuthName 完全一致):

# -c 创建文件;realm 为 "Secret Zone";用户名 aaa
htdigest -c /etc/httpd/digestpasswd "Secret Zone" aaa
# Adding password for aaa in realm Secret Zone.
# New password:
# Re-type new password:

# 追加用户(不加 -c)
htdigest /etc/httpd/digestpasswd "Secret Zone" bbb

配置:

<Directory "/www/secret">
    AuthType Digest
    AuthName "Secret Zone"
    AuthDigestProvider file
    AuthUserFile /etc/httpd/digestpasswd
    Require valid-user
</Directory>
chown root:apache /etc/httpd/digestpasswd
chmod 640 /etc/httpd/digestpasswd
httpd -t
systemctl restart httpd
  • AuthName 必须与 htdigest 创建时的 realm 字符串完全一致,否则永远认证失败。
  • Digest 认证的浏览器兼容性不如 Basic;真正的安全方案是 Basic 认证 + HTTPS。

4.7 目录列表控制与 autoindex

mod_autoindex 负责在目录中没有主页文件时生成目录列表。

满足以下条件时,目录会以列表形式展示:

  1. 该目录对应的 Options 中包含 Indexes;
  2. 该目录下没有 DirectoryIndex 指定的主页文件;
  3. 注释或删除 /etc/httpd/conf.d/welcome.conf(否则访问根目录会显示 RedHat/CentOS 欢迎页)。
# CentOS 6
/etc/init.d/httpd reload
elinks 127.0.0.1

让根目录列目录的另一种做法:编辑 /etc/httpd/conf.d/welcome.conf,把 Options -Indexes 中的 - 去掉:

<LocationMatch "^/+$">
    Options Indexes
    ErrorDocument 403 /.noindex.html
</LocationMatch>

关闭列目录(生产环境推荐):

<Directory "/www">
    Options -Indexes
</Directory>

技巧:在网站根目录下创建一个名为 index.html 的"目录",让 Apache 找不到同名主页文件从而列目录:

mkdir /var/www/html/index.html

然后浏览器访问服务器 IP,即可看到目录列表。

美化与本地化设置参见 3.10 例 6(IndexOptions、NameWidth=*、Charset=UTF-8、IndexIgnore)。

4.8 防盗链与 Referer 判断

盗链指其他站点直接引用本站的图片、视频等资源,消耗本站带宽。原理是判断请求头中的 Referer 是否属于本站。

方式一:mod_rewrite 实现(最常用)

<IfModule mod_rewrite.c>
    RewriteEngine On

    # 允许空 Referer(直接输入 URL、部分客户端不发 Referer)
    RewriteCond %{HTTP_REFERER} !^$
    # 允许来自本站的引用
    RewriteCond %{HTTP_REFERER} !^https?://(www\.)?test\.com/ [NC]
    # 只针对图片与视频类资源
    RewriteRule \.(jpg|jpeg|gif|png|bmp|mp4|flv|zip|rar)$ - [F,NC]
</IfModule>
  • [F] 表示直接返回 403 Forbidden。
  • 也可以把盗链请求重写到一张"禁止盗链"的提示图上:
RewriteRule \.(jpg|gif|png)$ /images/nolink.png [L,NC]

方式二:SetEnvIf + Require env(httpd 2.4)

SetEnvIfNoCase Referer "^https?://(www\.)?test\.com/" local_ref=1
SetEnvIf Referer "^$" local_ref=1

<FilesMatch "\.(jpg|jpeg|gif|png|mp4)$">
    Require env local_ref
</FilesMatch>

方式三:SetEnvIf + Order/Allow(httpd 2.2)

SetEnvIfNoCase Referer "^https?://(www\.)?test\.com/" local_ref=1
SetEnvIf Referer "^$" local_ref=1

<FilesMatch "\.(jpg|jpeg|gif|png|mp4)$">
    Order deny,allow
    Deny from all
    Allow from env=local_ref
</FilesMatch>

Referer 可被伪造,防盗链只能拦住普通盗用,不能作为安全边界。对付费/私密资源应使用带签名的临时 URL(token + 过期时间)。

4.9 隐藏版本信息与安全加固建议

# 1. 隐藏版本号与操作系统信息
ServerTokens Prod

# 2. 关闭错误页/目录列表底部的版本签名
ServerSignature Off

# 3. 关闭列目录,防止目录遍历
<Directory "/www">
    Options -Indexes +FollowSymLinks
</Directory>

# 4. 禁止访问根文件系统
<Directory />
    AllowOverride none
    Require all denied
    Options None
</Directory>

# 5. 关闭 TRACE 方法,防跨站追踪
TraceEnable Off

# 6. 只允许必要的 HTTP 方法
<Directory "/www">
    <LimitExcept GET POST HEAD>
        Require all denied
    </LimitExcept>
</Directory>

# 7. 禁止访问隐藏文件与备份文件
<FilesMatch "^\.|(\.(bak|old|sql|swp|inc|conf|log|git)|~)$">
    Require all denied
</FilesMatch>

# 8. 禁止上传目录执行脚本(防 webshell)
<Directory "/www/upload">
    Options -ExecCGI -Includes
    php_flag engine off
    <FilesMatch "\.(php|php5|phtml|pl|py|cgi|jsp)$">
        Require all denied
    </FilesMatch>
</Directory>

# 9. 限制请求大小,缓解慢速攻击与超大上传
LimitRequestBody 10485760
LimitRequestFieldSize 8190
LimitRequestLine 8190
Timeout 30

# 10. 添加安全响应头(需 mod_headers)
<IfModule mod_headers.c>
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Referrer-Policy "no-referrer-when-downgrade"
    # 仅 HTTPS 站点开启 HSTS
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    # 移除后端暴露的信息
    Header unset X-Powered-By
</IfModule>

# 11. 禁止直接用 IP 访问(只服务已知域名)
<VirtualHost *:80>
    ServerName default
    <Location />
        Require all denied
    </Location>
</VirtualHost>

其他加固措施:

  • 以低权限用户运行:确认 User apache / Group apache,绝不使用 root。
  • 网站目录权限:目录 755、文件 644,属主可用 root:apache,仅上传目录给 apache 写权限。
  • 及时打补丁:yum update httpd -y,关注 Apache 安全公告。
  • 关闭不必要的模块(见第六章性能调优)。
  • 保持 SELinux 开启并配置正确的上下文与布尔值。
  • 隐藏 PHP 版本:php.ini 中设置 expose_php = Off。
  • 生产禁用 .htaccess(AllowOverride None),既安全又提升性能。
  • 部署 HTTPS 并强制跳转,登录类页面必须走 HTTPS。

五、虚拟主机与常用模块

5.1 虚拟主机概念

虚拟主机(Virtual Host)指在一台服务器上运行多个 Web 站点。相关概念的演进关系:

虚拟主机(Web 服务器实现) -> VPS(虚拟化技术) -> 云服务器(云计算)
  • 虚拟主机:用 Web 服务器(Apache/Nginx)实现,一台服务器跑多个 Web 站点,共享同一套操作系统与资源。
  • VPS(Virtual Private Server,虚拟专用服务器):使用虚拟化技术(如 Virtual Server、VMware ESX Server)把一台物理服务器分隔成多个服务器,每个都拥有独立的 CPU、内存、磁盘资源和独立 IP。
  • 云服务器:基于云计算技术的弹性服务器,可按需扩缩容。

Apache 支持三种虚拟主机实现方式:

方式 优点 缺点
基于 IP 配置简单,直接用 IP 即可访问,独立 IP 隔离性好 IP 难记,浪费/占用 IP 地址资源
基于端口 不额外占用 IP 访问时需要加端口号,普通用户不会记也不会用端口
基于域名 一台服务器可跑多个域名站点,不浪费 IP、无需端口,最常用,适合外网直接访问 需要 DNS 支持,公网域名需要花钱购买

先准备两个站点目录:

mkdir -p /www/aaa
mkdir -p /www/bbb
echo "aaa main page" > /www/aaa/index.html
echo "bbb main page" > /www/bbb/index.html
chown -R apache:apache /www

推荐把虚拟主机配置写在单独的子配置文件中,例如 /etc/httpd/conf.d/vhost.conf 或 /etc/httpd/conf.d/vir.conf,而不是直接改 httpd.conf。

5.2 基于 IP 的虚拟主机

httpd 2.2 写法(老资料常见的 *:80 写法,配合不同 ServerName):

NameVirtualHost *:80

<VirtualHost *:80>
    DocumentRoot /www/aaa
    ServerName 10.0.0.2
    ErrorLog logs/1-error_log
    CustomLog logs/1-access_log common
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot /www/bbb
    ServerName 10.0.0.3
    ErrorLog logs/2-error_log
    CustomLog logs/2-access_log common
</VirtualHost>

httpd 2.4 推荐写法(把具体 IP 写进 <VirtualHost>):

<VirtualHost 10.0.0.2:80>
    DocumentRoot /www/aaa
    ServerName 10.0.0.2
    ErrorLog logs/aaa-error_log
    CustomLog logs/aaa-access_log common

    <Directory "/www/aaa">
        Options Indexes FollowSymLinks
        Require all granted
    </Directory>
</VirtualHost>

<VirtualHost 10.0.0.3:80>
    DocumentRoot /www/bbb
    ServerName 10.0.0.3
    ErrorLog logs/bbb-error_log
    CustomLog logs/bbb-access_log common

    <Directory "/www/bbb">
        Options Indexes FollowSymLinks
        Require all granted
    </Directory>
</VirtualHost>

基于 IP 区分站点时,<VirtualHost> 应写具体 IP 而非 *,否则两个块都匹配所有地址,实际只有第一个生效。每个 DocumentRoot 仍需对应的 <Directory> 容器授权(Require all granted)。

给网卡绑定第二个 IP:

# 临时生效(CentOS 6 网卡名 eth0,CentOS 7 常为 ens33)
ifconfig eth0:0 10.0.0.3 netmask 255.255.255.0
ifconfig ens33:0 10.0.0.3 netmask 255.255.255.0

# CentOS 7 推荐用 ip 命令
ip addr add 10.0.0.3/24 dev ens33

永久生效则写入网卡配置文件:

vim /etc/sysconfig/network-scripts/ifcfg-ens33
IPADDR1=10.0.0.2
IPADDR2=10.0.0.3
PREFIX1=24
PREFIX2=24
systemctl restart network
ip addr        # 检查虚拟 IP 是否生效(ifconfig 可能查不到用 ip 命令加的地址)

httpd -t
systemctl restart httpd
# CentOS 6: /etc/init.d/httpd restart

验证:

http://10.0.0.2/     -> aaa
http://10.0.0.3/     -> bbb
curl http://10.0.0.2/
curl http://10.0.0.3/

实际环境中每个 IP 应通过 DNS 对应自己的域名。

5.3 基于端口的虚拟主机

Listen 80       # 默认已有,不用重复添加
Listen 8000     # 让 Apache 监听额外端口

<VirtualHost *:80>
    DocumentRoot /www/aaa
    ServerName 10.0.0.2
    ErrorLog logs/1-error_log
    CustomLog logs/1-access_log common
</VirtualHost>

<VirtualHost *:8000>
    DocumentRoot /www/bbb
    ServerName 10.0.0.2
    ErrorLog logs/2-error_log
    CustomLog logs/2-access_log common
</VirtualHost>

httpd 2.4 中记得为各自的 DocumentRoot 补上 <Directory> 授权:

<Directory "/www/aaa">
    Options Indexes FollowSymLinks
    Require all granted
</Directory>

<Directory "/www/bbb">
    Options Indexes FollowSymLinks
    Require all granted
</Directory>
httpd -t
systemctl restart httpd
# CentOS 6: /etc/init.d/httpd restart

验证:

http://IP/           -> aaa
http://IP:8000/      -> bbb

非标准端口需同时放行防火墙,并注意 SELinux 限制 httpd 可监听的端口范围:

# CentOS 7
firewall-cmd --add-port=8000/tcp --permanent
firewall-cmd --reload

# SELinux 开启时,非默认端口需加入 http_port_t
semanage port -a -t http_port_t -p tcp 8000
semanage port -l | grep http_port_t

# CentOS 6
iptables -I INPUT -p tcp --dport 8000 -j ACCEPT
service iptables save

5.4 基于域名的虚拟主机

同一 IP、同一端口,通过请求头中的 Host 字段(即用户输入的域名)区分不同站点。需要 DNS 的 A 记录或 CNAME 记录支持,或在客户端 hosts 文件中手动添加解析。

5.4.1 httpd 2.2 配置

2.2 中必须先用 NameVirtualHost 声明"某个 IP:端口 用于名称虚拟主机",否则不生效:

NameVirtualHost *:80

<VirtualHost *:80>
    DocumentRoot /www/aaa
    ServerName aaa.cluster.com
    ErrorLog logs/1-error_log
    CustomLog logs/1-access_log common
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot /www/bbb
    ServerName bbb.cluster.com
    ErrorLog logs/2-error_log
    CustomLog logs/2-access_log common
</VirtualHost>

5.4.2 httpd 2.4 配置

<VirtualHost *:80>
    DocumentRoot /www/aaa
    ServerName aaa.test.com
    ServerAlias www.aaa.test.com
    ErrorLog logs/aaa-error_log
    CustomLog logs/aaa-access_log combined

    <Directory "/www/aaa">
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot /www/bbb
    ServerName bbb.test.com
    ErrorLog logs/bbb-error_log
    CustomLog logs/bbb-access_log combined

    <Directory "/www/bbb">
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>
</VirtualHost>

*httpd 2.4 中不需要(也不应再写)`NameVirtualHost :80**。该指令已废弃,写了也不起作用,只会在httpd -t时产生告警。基于域名的虚拟主机只要在同一 IP:端口 上定义多个<VirtualHost *:80>块并各自设置ServerName` 即可自动生效。老配置中该行常保留为注释:

# NameVirtualHost *:80

ServerAlias 可为同一站点配置多个别名域名,支持通配符:

ServerName test.com
ServerAlias www.test.com *.test.com

5.4.3 配合 BIND 搭建 DNS 验证

以下示例将 DNS 服务器和 Apache 部署在同一台机器(10.0.0.2)上,也可以分成两台。

安装 BIND:

yum install bind* -y

修改主配置文件:

vim /etc/named.conf
options {
    directory "/var/named";
    listen-on port 53 { any; };
    allow-query     { any; };
};

zone "test.com" IN {
    type master;
    file "data/test.com.zone";
};

zone "cluster.com" IN {
    type master;
    file "data/master.cluster.com.zone";
};

配置解析文件:

vim /var/named/data/test.com.zone
$TTL 86400
@       IN      SOA     test.com.   test.com. (
                        2018122001
                        120
                        240
                        360
                        86400 )
        IN      NS      10.0.0.2.
aaa     IN      A       10.0.0.2
bbb     IN      CNAME   aaa

CentOS 6 中 chroot 环境的区域文件路径示例:

# /var/named/chroot/var/named/data/master.cluster.com.zone
$TTL 86400
@       IN      SOA     sdfasdfsa.   sfddsafas. (
                        2011032601
                        120
                        240
                        360
                        86400 )
        IN      NS      10.0.0.2.
aaa     IN      A       10.0.0.2
bbb     IN      CNAME   aaa

重启/重载 DNS:

# CentOS 7
systemctl reload named
systemctl restart named
# CentOS 6
/etc/init.d/named restart

客户端将 DNS 指向自己搭建的服务器:

vim /etc/resolv.conf
nameserver 10.0.0.2

测试解析与连通性:

nslookup aaa.test.com
nslookup bbb.test.com
dig web1.abc.com
ping aaa.test.com

不搭 DNS 时用 hosts 文件验证(客户端 /etc/hosts,Windows 为 C:\Windows\System32\drivers\etc\hosts):

10.0.0.2   aaa.test.com
10.0.0.2   bbb.test.com
127.0.0.1  web1.abc.com
127.0.0.1  web2.abc.com

也可以用 curl 直接指定 Host 头,无需任何解析:

curl -H "Host: aaa.test.com" http://10.0.0.2/
curl -H "Host: bbb.test.com" http://10.0.0.2/
curl --resolve aaa.test.com:80:10.0.0.2 http://aaa.test.com/

最后重启 Apache 并验证:

httpd -t
httpd -S              # 查看虚拟主机匹配顺序,排错利器
systemctl restart httpd
http://aaa.test.com/     -> aaa
http://bbb.test.com/     -> bbb

5.4.4 用 Location 授权的虚拟主机写法

老资料中也有把授权写在 <Location /> 里的做法:

/* NameVirtualHost *:80 */
<VirtualHost *:80>
    DocumentRoot /etc
    ServerName etc.lab.net
    <Location />
        Options Indexes
        Order deny,allow
        Allow from all
    </Location>
</VirtualHost>
<VirtualHost *:80>
    DocumentRoot /www/web1
    ServerName web1.abc.com
    <Location />
        Options Indexes
        Order deny,allow
        Allow from all
    </Location>
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot /www/web2
    ServerName web2.abc.com
    <Location />
        Options Indexes
        Order deny,allow
        Allow from all
    </Location>
</VirtualHost>

5.5 三种虚拟主机方式的思考题

问题 1:两个域名的虚拟主机配置好后,直接用 IP 访问会访问到哪一个?

答:哪个虚拟主机配置在最前面,直接使用 IP(或用未匹配任何 ServerName 的域名)访问的就是哪一个。第一个 <VirtualHost> 块即该 IP:端口 的默认站点。

问题 2:配置虚拟主机后,原来 DocumentRoot 指向的网站家目录是否还可用?如果不可用如何解决?

答:不可用。除非再为原来的家目录单独建立一个虚拟主机。

可用 httpd -S 查看虚拟主机解析结果与匹配顺序,排查"访问到了错误站点"最有效。

5.6 日志按虚拟主机分离

在每个 <VirtualHost> 中单独指定 ErrorLog 与 CustomLog:

<VirtualHost *:80>
    ServerName aaa.test.com
    DocumentRoot /www/aaa
    ErrorLog  logs/aaa-error_log
    CustomLog logs/aaa-access_log combined
</VirtualHost>

<VirtualHost *:80>
    ServerName bbb.test.com
    DocumentRoot /www/bbb
    ErrorLog  logs/bbb-error_log
    CustomLog logs/bbb-access_log combined
</VirtualHost>

也可以所有站点写同一份日志,但格式中带上 %v(虚拟主机名),后续再用脚本拆分:

LogFormat "%v %h %l %u %t \"%r\" %>s %b" vcommon
CustomLog logs/access_log vcommon

日志合并:当日志分析软件只分析单个 access.log 时,需要把多个站点的日志按时间合并:

sort -m -k 4 -o newaccess.log access_log access_log1

-k 4 表示按日志的第 4 列(时间)排序合并,合并后的日志可供分析软件统一处理总流量。

5.7 日志轮转

方法一:使用 logrotate(系统自带,推荐)

CentOS/RHEL 默认通过 /etc/logrotate.d/httpd 管理 Apache 日志轮转。

cat /etc/logrotate.d/httpd
/var/log/httpd/*log {
    daily
    rotate 30
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        /bin/systemctl reload httpd.service > /dev/null 2>/dev/null || true
    endscript
}

CentOS 6 中 postrotate 段通常写作:

    postrotate
        /sbin/service httpd reload > /dev/null 2>/dev/null || true
    endscript

手动测试轮转:

logrotate -d /etc/logrotate.d/httpd     # 只演练不执行
logrotate -f /etc/logrotate.d/httpd     # 强制执行一次

方法二:自定义脚本

vim /usr/local/sbin/apache_log_rotate.sh

CentOS 6(PID 在 /etc/httpd/run/httpd.pid):

#!/bin/bash

mv /var/log/httpd/access_log /var/log/httpd/access_log.$(date -d "-1 days" +%F).log
touch /var/log/httpd/access_log
kill -HUP $(cat /etc/httpd/run/httpd.pid)

CentOS 7(PID 在 /run/httpd/httpd.pid):

#!/bin/bash

mv /var/log/httpd/access_log /var/log/httpd/access_log.$(date -d "-1 days" +%F).log
touch /var/log/httpd/access_log
kill -HUP $(cat /run/httpd/httpd.pid)

把脚本加入 crontab,每天 0 点执行:

chmod +x /usr/local/sbin/apache_log_rotate.sh
crontab -e
0 0 * * * /usr/local/sbin/apache_log_rotate.sh

kill -HUP 让 Apache 重新打开日志文件句柄,否则 Apache 仍会往被改名的旧文件里写。

方法三:使用 cronolog

cronolog(https://www.cronolog.org)是一个基于管道的日志轮转工具,可按日期自动分目录写入。

tar xvf cronolog-1.6.2.tar.gz -C /usr/src
cd /usr/src/cronolog-1.6.2/
./configure && make && make install

修改 httpd.conf,将日志输出通过管道交给 cronolog(每个虚拟主机需要单独配置):

# 将:
CustomLog logs/access_log combined
# 改为:
CustomLog "|/usr/local/sbin/cronolog /opt/%Y/%m/%d/access_log" combined

重启 Apache 后访问站点,会在 /opt/年/月/日/ 目录下生成日志文件。

方法四:使用 rotatelogs(Apache 自带)

rotatelogs 由 httpd-tools 提供,同样是管道方式:

# 每 86400 秒(一天)切一次,文件名带日期,+480 为时区偏移(分钟,东八区)
CustomLog "|/usr/sbin/rotatelogs -l /var/log/httpd/access_log.%Y-%m-%d 86400 480" combined

# 按大小切分:每 100M 切一次
ErrorLog "|/usr/sbin/rotatelogs /var/log/httpd/error_log.%Y-%m-%d 100M"

5.8 Alias 别名与 ScriptAlias

5.8.1 Alias

Alias 用于把 URL 路径映射到文件系统路径,可以把网站根目录以外的目录加入到 Web 访问路径中。

语法:

Alias URL-path file-path|directory-path

示例:把文件系统上的目录 /external/software 映射到 URL http://192.168.0.183/software:

Alias /software /external/software

<Directory "/external/software">
    Options Indexes
    Require all granted
</Directory>

另一个例子:

Alias /test "/test"

<Directory "/test">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>
mkdir /test
echo test > /test/index.html
chown -R apache:apache /test
chcon -t httpd_sys_content_t /test -R      # CentOS 7 开启 SELinux 时需要
systemctl restart httpd

浏览器访问 http://IP/test 查看效果。

httpd 2.2 的对应授权写法:

Alias /test  "/test"

<Directory "/test">
    Options Indexes MultiViews FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>

早期资料中有一种做法是把主配置里 <Directory /> 的默认拒绝注释掉(该指令出于安全考虑默认拒绝直接访问 /):

<Directory />
    AllowOverride none
    # Require all denied
</Directory>

Alias /test "/test"

不推荐这么做,正确做法是为 Alias 的目标目录单独写一个 <Directory> 容器授权,保留 <Directory /> 的默认拒绝。

AliasMatch(正则版本):

AliasMatch "^/icons/(.*)$" "/usr/share/icons/$1"

除了 Alias,也可以使用软链接实现类似效果(需要目标目录所在的 <Directory> 中有 FollowSymLinks 选项):

ln -s /test /www/test

5.8.2 ScriptAlias 与 CGI 支持

ScriptAlias 与 Alias 类似,但会把目标目录标记为"可执行 CGI 脚本目录"。默认配置:

ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"

<Directory "/var/www/cgi-bin">
    AllowOverride None
    Options None
    Require all granted
</Directory>

httpd 2.2 写法:

ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"

<Directory "/var/www/cgi-bin">
    AllowOverride None
    Options None
    Order allow,deny
    Allow from all
</Directory>

在普通目录中启用 CGI:需要 Options +ExecCGI 并用 AddHandler 声明扩展名。

LoadModule cgi_module modules/mod_cgi.so     # prefork 用 mod_cgi
# worker/event MPM 下应加载 mod_cgid
# LoadModule cgid_module modules/mod_cgid.so

<Directory "/www/cgi">
    Options +ExecCGI
    AddHandler cgi-script .cgi .pl .py .sh
    Require all granted
</Directory>

一个最简单的 CGI 脚本:

vim /var/www/cgi-bin/hello.cgi
#!/bin/bash
echo "Content-type: text/html"
echo ""
echo "<h1>Hello CGI</h1>"
echo "<p>Date: $(date)</p>"
chmod 755 /var/www/cgi-bin/hello.cgi
curl http://IP/cgi-bin/hello.cgi

CGI 脚本必须有可执行权限,且第一行输出必须是 HTTP 头(如 Content-type:)后跟一个空行,否则报 500 错误(Premature end of script headers)。

5.9 mod_rewrite 地址重写

mod_rewrite 基于正则表达式对 URL 进行重写与跳转,是 Apache 最强大的模块之一。

加载模块:

LoadModule rewrite_module modules/mod_rewrite.so
httpd -M | grep rewrite

5.9.1 核心指令

指令 说明
RewriteEngine On\|Off 开启/关闭重写引擎。每个虚拟主机、每个目录上下文都要单独开启,不会自动继承
RewriteRule 匹配模式 替换内容 [标志] 重写规则,把匹配的 URL 改写为目标
RewriteCond 测试串 条件模式 [标志] 重写条件,写在 RewriteRule 之前,多条默认为"与"关系
RewriteBase / 指定重写的 URL 基准路径,在 .htaccess 中使用时常需要
RewriteMap 定义外部映射表(文件、程序、dbm)
RewriteOptions Inherit 让子配置继承父配置的重写规则
RewriteLog / RewriteLogLevel 2.2 的重写日志;2.4 改用 LogLevel rewrite:trace3

重写规则的正则要点:

符号 含义
^ 行首
$ 行尾
. 任意单个字符
* 前面的字符出现 0 次或多次
+ 前面的字符出现 1 次或多次
? 前面的字符出现 0 次或 1 次
[abc] 字符集合中的任意一个
[^abc] 不在集合中的任意字符
(...) 分组,在替换串中用 $1、$2 引用
\| 或
\ 转义
! 在 RewriteCond 的条件模式前表示取反
$1..$9 引用 RewriteRule 匹配模式中的分组
%1..%9 引用最近一条 RewriteCond 中的分组
%{变量名} 引用服务器变量

常用服务器变量:

变量 说明
%{HTTP_HOST} 请求的主机名(域名)
%{HTTP_USER_AGENT} 客户端标识
%{HTTP_REFERER} 来源页
%{HTTP_COOKIE} Cookie
%{REQUEST_URI} 请求的 URI(不含域名,含 / 开头)
%{REQUEST_FILENAME} 请求映射到的完整文件系统路径
%{QUERY_STRING} 查询字符串(? 后面的部分)
%{REQUEST_METHOD} 请求方法
%{REMOTE_ADDR} 客户端 IP
%{SERVER_PORT} 服务器端口
%{SERVER_NAME} 服务器名
%{HTTPS} 是否 HTTPS,取值 on / off
%{TIME_HOUR} 当前小时(可做按时间分流)

5.9.2 常用标志位

标志 全称 说明
L Last 本条规则匹配后停止处理后续规则(最常用,几乎每条规则末尾都要带)
R / R=301 / R=302 Redirect 发起外部重定向(浏览器地址栏会变)。默认 302 临时跳转,R=301 为永久跳转
NC NoCase 匹配时忽略大小写
QSA Query String Append 把原请求的查询字符串追加到新 URL 后面,而不是覆盖
QSD Query String Discard 丢弃原查询字符串(2.4)
F Forbidden 直接返回 403 Forbidden(防盗链常用)
G Gone 返回 410 Gone
P Proxy 通过 mod_proxy 转发到后端(反向代理)
PT Pass Through 把重写结果交给后续的 Alias/ScriptAlias 等处理
NE No Escape 不对输出中的特殊字符做 URL 转义(如 #、&)
S=n Skip 跳过接下来 n 条规则
C Chain 与下一条规则串联,本条不匹配则下一条也跳过
E=var:val Env 设置环境变量
T=MIME Type 强制指定 MIME 类型
OR Or 用于 RewriteCond,表示与下一条条件是"或"关系
NS NoSubreq 只对主请求生效,不处理内部子请求

5.9.3 案例一:域名跳转(不带 www 跳到带 www)

<VirtualHost *:80>
    ServerName test.com
    ServerAlias www.test.com
    DocumentRoot /www/test

    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{HTTP_HOST} ^test\.com$ [NC]
        RewriteRule ^/(.*)$ http://www.test.com/$1 [L,R=301]
    </IfModule>
</VirtualHost>

反向(带 www 跳到不带 www):

RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.test\.com$ [NC]
RewriteRule ^/(.*)$ http://test.com/$1 [L,R=301]

旧域名整站跳新域名:

RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?old\.com$ [NC]
RewriteRule ^/(.*)$ http://www.new.com/$1 [L,R=301]

R=301 是永久重定向,搜索引擎会把权重转到新地址,适合域名更换;调试阶段建议先用 R=302,因为 301 会被浏览器长期缓存,配置写错很难清掉。

5.9.4 案例二:伪静态

把 /article-123.html 重写为 /article.php?id=123(用户看到的是静态地址,实际执行 PHP):

<Directory "/www/html">
    Options +FollowSymLinks
    AllowOverride None

    RewriteEngine On
    RewriteRule ^article-([0-9]+)\.html$ /article.php?id=$1 [L]
    RewriteRule ^list-([0-9]+)-([0-9]+)\.html$ /list.php?cid=$1&page=$2 [L,QSA]
</Directory>

伪静态是内部重写,不带 R 标志,浏览器地址栏不变。注意重写规则所在上下文:在 <Directory> 或 .htaccess 中,匹配模式不以 / 开头(已去掉目录前缀);在虚拟主机/服务器级别,匹配模式以 / 开头。

典型的"前端控制器"写法(Laravel/ThinkPHP 等框架通用),文件/目录存在时直接返回,否则统一交给 index.php:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L]

RewriteCond 常用文件测试符:

测试符 含义
-f 是存在的普通文件
-d 是存在的目录
-s 是存在且大小非 0 的文件
-l 是符号链接
-x 有可执行权限
!-f 文件不存在(! 取反)

5.9.5 案例三:强制 HTTPS

方式一(mod_rewrite,适用于无法确定端口的情况):

<VirtualHost *:80>
    ServerName www.test.com
    DocumentRoot /www/test

    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule ^/?(.*)$ https://%{HTTP_HOST}/$1 [L,R=301]
</VirtualHost>

等价写法:

RewriteEngine On
RewriteCond %{SERVER_PORT} ^80$
RewriteRule ^(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]

方式二(更轻量,推荐;无需 mod_rewrite):

<VirtualHost *:80>
    ServerName www.test.com
    Redirect permanent / https://www.test.com/
</VirtualHost>

配合 HSTS 让浏览器后续自动使用 HTTPS(只在 443 的虚拟主机里加):

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>

5.9.6 其他常用重写案例

RewriteEngine On

# 1. 禁止指定 User-Agent(拦爬虫)
RewriteCond %{HTTP_USER_AGENT} (bot|spider|crawler|curl|wget) [NC]
RewriteRule ^.* - [F]

# 2. 目录整体迁移
RewriteRule ^/old/(.*)$ /new/$1 [L,R=301]

# 3. 按 IP 引导到维护页(排除运维自己的 IP)
RewriteCond %{REMOTE_ADDR} !^192\.168\.0\.100$
RewriteCond %{REQUEST_URI} !^/maintenance\.html$
RewriteRule ^.*$ /maintenance.html [L]

# 4. 移动端跳转到 m 站
RewriteCond %{HTTP_USER_AGENT} (android|iphone|ipad|mobile) [NC]
RewriteRule ^/$ http://m.test.com/ [L,R=302]

# 5. 反向代理到后端(需 mod_proxy)
RewriteRule ^/api/(.*)$ http://127.0.0.1:8080/api/$1 [P,L]

# 6. 屏蔽某类文件的直接访问
RewriteRule \.(sql|bak|log|inc)$ - [F,NC,L]

5.9.7 重写调试

httpd 2.2:

RewriteLog /var/log/httpd/rewrite.log
RewriteLogLevel 3

httpd 2.4(RewriteLog 已移除,改用 LogLevel):

LogLevel warn rewrite:trace3
tail -f /var/log/httpd/error_log

trace 级别从 trace1 到 trace8,数字越大越详细;调试完记得改回 LogLevel warn,否则日志暴涨。

5.10 mod_deflate 压缩

mod_deflate 在响应发送前对文本类内容做 gzip 压缩,可大幅减少传输量(HTML/CSS/JS 通常能压到 20%~30%)。

LoadModule deflate_module modules/mod_deflate.so
<IfModule mod_deflate.c>
    # 按 MIME 类型开启压缩
    AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
    AddOutputFilterByType DEFLATE application/javascript application/x-javascript
    AddOutputFilterByType DEFLATE application/json application/xml
    AddOutputFilterByType DEFLATE application/rss+xml image/svg+xml

    # 压缩级别 1~9,默认 6;越高 CPU 消耗越大
    DeflateCompressionLevel 6

    # 内存缓冲区与窗口大小
    DeflateBufferSize 8096
    DeflateWindowSize 15

    # 老浏览器兼容:IE6 以下不压缩
    BrowserMatch ^Mozilla/4         gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch \bMSIE\ [456]      !no-gzip !gzip-only-text/html

    # 已压缩的二进制文件不再压缩(图片、视频、压缩包)
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|ico|zip|gz|bz2|rar|7z|mp3|mp4|flv|pdf)$ no-gzip dont-vary

    # 让代理/CDN 按 Accept-Encoding 区分缓存
    Header append Vary User-Agent env=!dont-vary

    # 在日志中记录压缩比(可选)
    DeflateFilterNote Input  instream
    DeflateFilterNote Output outstream
    DeflateFilterNote Ratio  ratio
    LogFormat '"%r" %{outstream}n/%{instream}n (%{ratio}n%%)' deflate
    CustomLog logs/deflate_log deflate
</IfModule>

验证压缩是否生效(响应头中应出现 Content-Encoding: gzip):

curl -I -H "Accept-Encoding: gzip,deflate" http://IP/index.html
curl -s -H "Accept-Encoding: gzip" -o /dev/null -w "%{size_download}\n" http://IP/index.html

压缩用 CPU 换带宽。图片、视频、已压缩的归档文件不要再压;CPU 紧张时可把压缩级别降到 3~4。

5.11 mod_expires 浏览器缓存

mod_expires 为静态资源添加 Expires 与 Cache-Control: max-age 响应头,让浏览器在有效期内直接使用本地缓存,不再发请求。

LoadModule expires_module modules/mod_expires.so
<IfModule mod_expires.c>
    ExpiresActive On

    # 默认策略:从访问时刻起缓存 1 天
    ExpiresDefault "access plus 1 day"

    # 按 MIME 类型分别设置
    ExpiresByType text/html                "access plus 0 seconds"
    ExpiresByType text/css                 "access plus 7 days"
    ExpiresByType application/javascript   "access plus 7 days"
    ExpiresByType image/jpeg               "access plus 1 month"
    ExpiresByType image/png                "access plus 1 month"
    ExpiresByType image/gif                "access plus 1 month"
    ExpiresByType image/x-icon             "access plus 1 year"
    ExpiresByType application/font-woff    "access plus 1 year"
    ExpiresByType application/pdf          "access plus 1 month"
</IfModule>

时间基准与单位:

  • 基准:access(等同 now,从客户端访问时刻算)或 modification(从文件最后修改时间算)。
  • 单位:years、months、weeks、days、hours、minutes、seconds。
  • 也可以用简写语法:ExpiresByType image/gif A2592000(A = access,M = modification,后跟秒数)。

配合 mod_headers 精细控制:

<IfModule mod_headers.c>
    # 静态资源长期缓存
    <FilesMatch "\.(ico|jpg|jpeg|png|gif|css|js|woff2?)$">
        Header set Cache-Control "max-age=2592000, public"
    </FilesMatch>

    # 动态页面禁止缓存
    <FilesMatch "\.(php|jsp|cgi|html)$">
        Header set Cache-Control "no-cache, no-store, must-revalidate"
        Header set Pragma "no-cache"
        Header set Expires 0
    </FilesMatch>
</IfModule>

验证:

curl -I http://IP/logo.png
# 应看到 Expires 与 Cache-Control: max-age=2592000

长缓存的资源更新后客户端拿不到新版本,解决办法是给静态资源加版本号或哈希指纹(如 app.a1b2c3.js、style.css?v=20260822)。

5.12 mod_status 状态页

mod_status 提供 Apache 运行状态页面,展示当前连接数、请求处理情况、每个 worker 的状态、CPU 占用等,是排障与调优的重要依据。

LoadModule status_module modules/mod_status.so

httpd 2.4 配置(放在 /etc/httpd/conf.d/status.conf):

ExtendedStatus On

<Location /server-status>
    SetHandler server-status
    Require local
    Require ip 192.168.0.0/24
</Location>

httpd 2.2 配置:

ExtendedStatus On

<Location /server-status>
    SetHandler server-status
    Order deny,allow
    Deny from all
    Allow from 127.0.0.1
    Allow from 192.168.0.0/24
</Location>

也可以叠加密码认证进一步限制:

<Location /server-status>
    SetHandler server-status
    AuthType Basic
    AuthName "Server Status"
    AuthUserFile /etc/httpd/userpasswd
    <RequireAll>
        Require ip 192.168.0.0/24
        Require valid-user
    </RequireAll>
</Location>

访问方式:

# 浏览器:http://IP/server-status
curl http://127.0.0.1/server-status
curl http://127.0.0.1/server-status?auto     # 机器可读格式,适合监控采集
apachectl status                              # 需要 lynx 或 elinks
apachectl fullstatus

?auto 输出关键指标:

Total Accesses: 12345
Total kBytes: 67890
CPULoad: 1.23
Uptime: 86400
ReqPerSec: 0.142
BytesPerSec: 804
BusyWorkers: 3
IdleWorkers: 5
Scoreboard: _W_.___K...

Scoreboard 各字符含义:

字符 状态
_ 等待连接(空闲 worker)
S 启动中
R 读取请求
W 发送响应
K Keep-Alive 保持中
D DNS 查询中
C 关闭连接
L 写日志
G 优雅退出中
I 空闲清理
. 该槽位尚未使用

另有 mod_info 可查看完整配置信息(同样要严格限制访问):

<Location /server-info>
    SetHandler server-info
    Require local
</Location>

安全:/server-status 与 /server-info 会暴露服务器内部信息(正在访问的 URL、客户端 IP、配置细节),必须限制来源 IP,绝不能对公网开放。

5.13 mod_ssl 配置 HTTPS

HTTP 明文传输,账号密码与页面内容都可被抓包还原。HTTPS 在 HTTP 与 TCP 之间加入 TLS 层完成加密与身份校验,默认端口 443。

5.13.1 安装模块和工具

yum install mod_ssl openssl -y
sudo yum install mod_ssl openssl -y

安装后会自动生成 /etc/httpd/conf.d/ssl.conf,并加载 mod_ssl,httpd 开始监听 443:

httpd -M | grep ssl
ss -ntlp | grep 443

5.13.2 生成私钥与自签名证书

分步生成(交互式):

cd /etc/pki/tls

# 1. 生成 2048 位 RSA 私钥
openssl genrsa -out private/localhost.key 2048
# 老写法:openssl genrsa -out ca.key 2048

# 2. 用私钥生成证书签名请求(CSR),交互式填写信息
#    Common Name (CN) 必须填写站点域名,如 www.test.com,否则浏览器提示域名不匹配
openssl req -new -key private/localhost.key -out localhost.csr
# 老写法:openssl req -new -key ca.key -out ca.csr

# 3. 用私钥对 CSR 自签名,生成有效期 365 天的证书
openssl x509 -req -days 365 -in localhost.csr \
    -signkey private/localhost.key -out certs/localhost.crt
# 老写法:openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

一条命令完成(非交互,最常用):

openssl req -newkey rsa:2048 -nodes -keyout /etc/pki/tls/private/localhost.key \
    -x509 -days 365 -out /etc/pki/tls/certs/localhost.crt \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=Test/OU=IT/CN=www.test.com"

若证书文件是在别处生成的,移动并重命名到标准位置:

sudo mv ca.crt /etc/pki/tls/certs/localhost.crt
sudo mv ca.key /etc/pki/tls/private/localhost.key

收紧私钥权限:

chmod 600 /etc/pki/tls/private/localhost.key
chown root:root /etc/pki/tls/private/localhost.key

查看证书内容:

openssl x509 -in /etc/pki/tls/certs/localhost.crt -noout -text
openssl x509 -in /etc/pki/tls/certs/localhost.crt -noout -dates -subject

# 校验私钥与证书是否匹配(两个 MD5 值应相同)
openssl rsa  -in /etc/pki/tls/private/localhost.key -noout -modulus | openssl md5
openssl x509 -in /etc/pki/tls/certs/localhost.crt   -noout -modulus | openssl md5

5.13.3 编辑 ssl.conf

vi /etc/httpd/conf.d/ssl.conf

最小改动:只需把证书与私钥路径指到实际文件:

SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key

完整的关键配置段:

Listen 443 https

SSLPassPhraseDialog  builtin
SSLSessionCache      shmcb:/run/httpd/sslcache(512000)
SSLSessionCacheTimeout  300
SSLRandomSeed startup file:/dev/urandom  256
SSLRandomSeed connect builtin
SSLCryptoDevice builtin

<VirtualHost _default_:443>
    ServerName www.test.com:443
    DocumentRoot "/var/www/html"

    SSLEngine on
    SSLCertificateFile    /etc/pki/tls/certs/localhost.crt
    SSLCertificateKeyFile /etc/pki/tls/private/localhost.key

    # 由 CA 签发时还需指定中间证书链
    #SSLCertificateChainFile /etc/pki/tls/certs/ca-chain.crt
    # CA 根证书(做双向认证时使用)
    #SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt

    # 禁用不安全的老协议
    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite HIGH:!aNULL:!MD5:!3DES:!RC4
    SSLHonorCipherOrder on

    ErrorLog  logs/ssl_error_log
    TransferLog logs/ssl_access_log
    CustomLog logs/ssl_access_log combined
    LogLevel warn

    <Directory "/var/www/html">
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>

    # CGI 目录下关闭 SSL 变量导出限制
    <Files ~ "\.(cgi|shtml|phtml|php3?)$">
        SSLOptions +StdEnvVars
    </Files>

    # 老 IE 兼容
    BrowserMatch "MSIE [2-5]" nokeepalive ssl-unclean-shutdown \
        downgrade-1.0 force-response-1.0
</VirtualHost>
指令 说明
Listen 443 https 监听 443 端口(mod_ssl 安装后通常会自动添加)
SSLEngine on 在该虚拟主机上启用 SSL/TLS
SSLCertificateFile 证书(公钥)文件路径
SSLCertificateKeyFile 私钥文件路径
SSLCertificateChainFile 中间 CA 证书链,自签名时不需要,商业证书通常必须配置
SSLCACertificateFile CA 根证书,用于验证客户端证书(双向认证)
SSLProtocol 允许的协议版本,应禁用 SSLv2/SSLv3(POODLE)与 TLSv1.0/1.1
SSLCipherSuite 允许的加密套件,禁用弱算法
SSLHonorCipherOrder on 优先使用服务器指定的套件顺序
SSLSessionCache 会话缓存,减少完整握手次数
SSLVerifyClient 客户端证书校验级别(双向认证时设为 require)

多域名 HTTPS(SNI,Server Name Indication):httpd 2.2.12+ 与 2.4 支持同一 IP 上多个 HTTPS 虚拟主机各用自己的证书:

<VirtualHost *:443>
    ServerName a.test.com
    SSLEngine on
    SSLCertificateFile    /etc/pki/tls/certs/a.test.com.crt
    SSLCertificateKeyFile /etc/pki/tls/private/a.test.com.key
    DocumentRoot /www/a
</VirtualHost>

<VirtualHost *:443>
    ServerName b.test.com
    SSLEngine on
    SSLCertificateFile    /etc/pki/tls/certs/b.test.com.crt
    SSLCertificateKeyFile /etc/pki/tls/private/b.test.com.key
    DocumentRoot /www/b
</VirtualHost>

5.13.4 放行防火墙并重启

# CentOS 7
firewall-cmd --add-service=https --permanent
firewall-cmd --reload

# 证书路径若为自定义目录,需设置 SELinux 上下文
restorecon -Rv /etc/pki/tls

httpd -t
systemctl restart httpd
# CentOS 6: /etc/init.d/httpd restart

HTTPS 默认端口为 443,应确保 httpd.conf 或 ssl.conf 中监听了该端口。

验证:

curl -Ik https://IP
curl -Ik https://www.test.com

# 查看证书信息与有效期
openssl s_client -connect IP:443 -servername www.test.com </dev/null 2>/dev/null \
    | openssl x509 -noout -dates -subject

# 查看协商出的协议与套件
openssl s_client -connect IP:443 -tls1_2 </dev/null

之后可通过 https://服务器IP/ 或 https://域名/ 访问。

5.13.5 HTTP 强制跳转 HTTPS

在 80 端口的虚拟主机中重定向(推荐写法):

<VirtualHost *:80>
    ServerName www.test.com
    Redirect permanent / https://www.test.com/
</VirtualHost>

mod_rewrite 写法与 HSTS 见 5.9.5。

关于自签名证书:

  • 自签名证书不在浏览器信任的根 CA 列表中,访问时会出现"不安全 / 证书无效"警告,需手动点击继续,仅适用于内网测试与实验环境。
  • 生产环境应使用受信任 CA 签发的证书,或使用免费的 Let's Encrypt:
yum install epel-release -y
yum install certbot python2-certbot-apache -y
certbot --apache -d www.test.com -d test.com
certbot renew --dry-run

Let's Encrypt 证书有效期 90 天,可用 certbot renew 配合 cron 或 systemd timer 自动续签。

  • 证书到期会导致全站不可访问,务必监控有效期。

5.14 扩展:mod_cband 限速模块

使用第三方模块 mod-cband 可以对 Apache 进行带宽和连接数限制。

5.14.1 编译安装

wget http://ncu.dl.sourceforge.net/project/cband/mod-cband/mod-cband-0.9.6.1/mod-cband-0.9.6.1.tgz

tar xvf mod-cband-0.9.7.2.tgz -C /usr/src/
cd /usr/src/mod-cband-0.9.7.2/
./configure && make && make install

若报错 configure: error: apxs missing,需安装 httpd-devel(提供 apxs):

yum install httpd-devel -y

确认 httpd.conf 中已加载模块:

LoadModule cband_module /usr/lib/httpd/modules/mod_cband.so

5.14.2 配置限速

针对虚拟主机 news.cluster.com 进行限速:

NameVirtualHost *:80
<VirtualHost *:80>
    DocumentRoot /www/aaa
    ServerName news.cluster.com
    ErrorLog logs/aaa.com-error_log
    CustomLog logs/aaa.com-access_log common

    cbandlimit 100M              # 网站允许的总下载流量
    cbandperiod 1W               # 流量统计清空周期(1 周)
    cbandspeed 1024 10 30        # 总带宽 1024Kbps(除以 8 即 KB/s),每秒 10 个连接,最大总连接数 30
    cbandremotespeed 10kb/s 3 3  # 单客户端下载速度 10KB/s,每秒 3 个连接,单 IP 最多 3 个连接

    <Location /cband-status>
        sethandler cband-status
    </Location>
    <Location /cband-status-me>
        sethandler cband-status-me
    </Location>
</VirtualHost>
/etc/init.d/httpd restart

测试:

# 创建一个 1000MB 的大文件用于下载测试
dd if=/dev/zero of=/www/aaa/123 bs=1M count=1000

# 使用 wget 下载,观察限速效果
wget http://news.cluster.com/123

状态页面:

  • http://news.cluster.com/cband-status
  • http://news.cluster.com/cband-status-me

httpd 2.4 上 mod_cband 已基本不再维护,可改用 mod_ratelimit(Apache 自带)做简单限速:

<Location /download>
    SetOutputFilter RATE_LIMIT
    SetEnv rate-limit 400        # 限速 400 KB/s
</Location>

并发连接数限制可使用第三方 mod_limitipconn 或 mod_qos,或直接在防火墙层用 iptables 的 connlimit 模块。


六、与 PHP 集成、性能调优与排错

6.1 Apache 集成 PHP 的两种方式

详见 10-05-LAMP与LNMP架构搭建.md

Apache 本身只能返回静态文件,要执行 PHP 需要引入 PHP 解释器。有两种主流做法。

6.1.1 方式一:mod_php(把 PHP 嵌入 Apache 进程)

安装:

yum install php php-mysql php-gd php-mbstring -y
systemctl restart httpd

安装后会自动生成 /etc/httpd/conf.d/php.conf,核心内容:

# httpd 2.2 / 2.4 通用
<FilesMatch "\.php$">
    SetHandler application/x-httpd-php
</FilesMatch>

# 老式写法(2.2 常见)
AddHandler php5-script .php
AddType text/html .php

# 让 index.php 也能作为目录默认主页
DirectoryIndex index.php index.html

php_value session.save_handler "files"
php_value session.save_path    "/var/lib/php/session"

在 <Directory> 或 .htaccess 中还可以用 php_value / php_flag 覆盖部分 php.ini 设置(mod_php 独有能力):

<Directory "/www/app">
    php_value upload_max_filesize 20M
    php_value post_max_size 20M
    php_value memory_limit 256M
    php_flag display_errors off
</Directory>

# 上传目录禁止执行 PHP
<Directory "/www/upload">
    php_flag engine off
</Directory>

验证:

echo '<?php phpinfo(); ?>' > /var/www/html/info.php
curl http://127.0.0.1/info.php | head
httpd -M | grep php
php -v

测试完必须删除 info.php,phpinfo() 会泄露服务器全部环境信息。

mod_php 的优劣:

优点 缺点
配置最简单,装包即用 PHP 解释器嵌进每个 httpd 进程,即使处理静态文件的进程也占用 PHP 的内存,内存开销大
PHP 与 Apache 同进程,无进程间通信开销,小流量下响应快 必须使用 prefork MPM(PHP 及其扩展多数不线程安全),无法使用 worker/event,高并发能力受限
可用 php_value / php_flag 在 Apache 配置中调整 PHP 参数 PHP 崩溃会影响 Apache 进程;PHP 以 apache 用户运行,无法按站点隔离权限
无需额外维护一个服务 升级 PHP 必须重启 Apache;无法多版本 PHP 共存

适用场景:单机小站点、内部系统、传统 LAMP 教学环境。

6.1.2 方式二:php-fpm + mod_proxy_fcgi(推荐)

PHP 以独立的 FastCGI 进程池(php-fpm)运行,Apache 通过 mod_proxy_fcgi 把 .php 请求转发过去。

安装并启动 php-fpm:

yum install php-fpm php-mysql php-gd php-mbstring -y
systemctl start php-fpm
systemctl enable php-fpm

# 查看监听方式
ss -ntlp | grep 9000            # TCP 模式
ls -l /run/php-fpm/www.sock     # UNIX socket 模式

php-fpm 关键配置(/etc/php-fpm.d/www.conf):

user = apache
group = apache

; TCP 监听
listen = 127.0.0.1:9000
; 或 UNIX socket(同机性能更好)
;listen = /run/php-fpm/www.sock
;listen.owner = apache
;listen.group = apache
;listen.mode = 0660

listen.allowed_clients = 127.0.0.1

pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500

slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5s
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on

Apache 侧配置(httpd 2.4,加载 mod_proxy 与 mod_proxy_fcgi):

LoadModule proxy_module        modules/mod_proxy.so
LoadModule proxy_fcgi_module   modules/mod_proxy_fcgi.so
<VirtualHost *:80>
    ServerName www.test.com
    DocumentRoot /www/test
    DirectoryIndex index.php index.html

    # 写法一:SetHandler + proxy:fcgi(2.4.9+ 推荐,支持路径信息)
    <FilesMatch "\.php$">
        SetHandler "proxy:fcgi://127.0.0.1:9000"
    </FilesMatch>

    # 写法一(UNIX socket,2.4.10+)
    #<FilesMatch "\.php$">
    #    SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
    #</FilesMatch>

    # 写法二:ProxyPassMatch(老一些的 2.4 版本)
    #ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/www/test/$1

    <Directory "/www/test">
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>
</VirtualHost>
httpd -t
systemctl restart httpd
setsebool -P httpd_can_network_connect on    # SELinux 开启时必做

httpd 2.2 上没有 mod_proxy_fcgi,对接 php-fpm 需使用第三方 mod_fastcgi 或 mod_fcgid:

LoadModule fcgid_module modules/mod_fcgid.so
AddHandler fcgid-script .php
FcgidWrapper /usr/bin/php-cgi .php

php-fpm 的优劣:

优点 缺点
Web 服务与 PHP 解耦,可使用 event/worker MPM,并发能力强 需要多维护一个服务,配置步骤更多
Apache 处理静态资源的进程不含 PHP,内存占用显著降低 每次请求有一次进程间通信(socket)开销
PHP 进程池可独立调优、独立重启,升级 PHP 不影响 Apache 需要额外关注 php-fpm 的 pm.max_children 等参数与 502 排查
可为不同站点起多个 fpm 池,各用不同的运行用户,实现权限隔离 SELinux 下需放开 httpd_can_network_connect
可多版本 PHP 共存(不同池监听不同端口/socket) php_value 不再能写在 Apache 配置中,须改用 php_admin_value[...] 写在池配置里

适用场景:中高并发、多站点、需要 PHP 多版本或权限隔离的生产环境。

6.2 性能调优

6.2.1 MPM 参数调整

prefork:一个连接一个进程,关键是算清单进程内存占用。

<IfModule mpm_prefork_module>
    StartServers            10
    MinSpareServers         10
    MaxSpareServers         20
    ServerLimit            512
    MaxRequestWorkers      300
    MaxConnectionsPerChild 4000
</IfModule>

估算 MaxRequestWorkers:

# 1. 查看单个 httpd 进程的实际内存占用(RSS,单位 KB)
ps -ylC httpd --sort:rss | awk '{sum+=$8; n++} END {print "平均 RSS(MB):", sum/n/1024}'

# 2. 查看可用内存
free -m

MaxRequestWorkers ≈ (可用于 Apache 的内存 MB) / (单进程平均 RSS MB),再留 20%~30% 余量。例如可用 4GB、单进程 25MB,则约为 4096 × 0.8 / 25 ≈ 130。

worker / event:

<IfModule mpm_event_module>
    StartServers             4
    ServerLimit             16
    ThreadLimit             64
    ThreadsPerChild         64
    MinSpareThreads        100
    MaxSpareThreads        400
    MaxRequestWorkers     1024      # = ServerLimit × ThreadsPerChild
    MaxConnectionsPerChild   0
</IfModule>

参数关系与注意点:

  • prefork:MaxRequestWorkers ≤ ServerLimit;若要设 MaxRequestWorkers 300,必须先把 ServerLimit 提到 ≥ 300。
  • worker/event:MaxRequestWorkers ≤ ServerLimit × ThreadsPerChild,且应为 ThreadsPerChild 的整数倍;ThreadsPerChild ≤ ThreadLimit。
  • ServerLimit 与 ThreadLimit 是硬上限,修改后必须完整 restart(reload/graceful 不生效)。
  • MaxConnectionsPerChild 设为非 0(如 4000)可周期性回收进程规避内存泄漏;代码质量可靠时设 0 减少进程重建开销。
  • 并发打满的表现:错误日志出现 server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting,同时 /server-status 中 IdleWorkers 长期为 0。

6.2.2 连接数与文件描述符

Timeout 30                       # 从默认 60 降低,尽快释放异常连接
KeepAlive On
KeepAliveTimeout 3               # 从 15 降到 2~5 秒,避免线程被空闲连接长时间占用
MaxKeepAliveRequests 200
HostnameLookups Off              # 关闭反解,避免每个请求都做 DNS 查询
EnableSendfile on                # 本地文件系统上开启零拷贝
EnableMMAP on

系统层面(每个连接至少占一个文件描述符):

# 查看当前限制
ulimit -n
cat /proc/sys/fs/file-max

# 临时提高
ulimit -n 65535

# 永久提高
vim /etc/security/limits.conf
*  soft  nofile  65535
*  hard  nofile  65535

CentOS 7 中 systemd 管理的服务不受 limits.conf 约束,需单独配置:

mkdir -p /etc/systemd/system/httpd.service.d
vim /etc/systemd/system/httpd.service.d/limits.conf
[Service]
LimitNOFILE=65535
systemctl daemon-reload
systemctl restart httpd

# 验证生效值
cat /proc/$(pgrep -o httpd)/limits | grep "open files"

内核网络参数(/etc/sysctl.conf):

net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 4096
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 10000 65000
net.core.netdev_max_backlog = 4096
sysctl -p

Apache 的监听队列长度:

ListenBacklog 1024

6.2.3 关闭不必要的模块

每个加载的模块都会占用内存并可能增加处理开销。查看当前已加载模块:

httpd -M
httpd -M | wc -l

CentOS 7 中注释掉 /etc/httpd/conf.modules.d/00-base.conf 里用不到的行即可。常见可关闭的模块:

模块 用途 不需要时可关闭
mod_userdir 用户个人主页 ~user 通常不用
mod_autoindex 生成目录列表 生产站点通常禁止列目录
mod_negotiation 内容协商(MultiViews) 不用多语言协商时可关
mod_status / mod_info 状态页 若不用监控可关(用则严格限权)
mod_cgi / mod_cgid CGI 支持 纯 PHP 站点不需要
mod_include SSI 通常不需要,且有安全风险
mod_dav / mod_dav_fs WebDAV 绝大多数场景不需要,建议关闭
mod_auth_digest Digest 认证 只用 Basic 时可关
mod_proxy_* 各类代理 不做反代时只留需要的子模块
mod_ssl HTTPS 纯内网 HTTP 服务可关

必须保留的核心模块:mod_mime、mod_dir、mod_log_config、mod_authz_core、mod_authz_host、mod_unixd、mod_mpm_*、mod_alias、mod_headers(若用安全头)。

httpd -t              # 关模块后一定要做语法检查,避免依赖缺失
systemctl restart httpd

注意依赖关系:例如关闭 mod_authz_core 会导致 Require 无法使用;关闭 mod_mime 会导致所有文件都以默认类型返回。

6.2.4 静态资源与反向代理分工

典型的高性能架构分工:

客户端 -> CDN -> Nginx(静态资源 + 反向代理 + 负载均衡) -> Apache/Tomcat/php-fpm(动态请求)
  • 图片、CSS、JS、字体等静态资源交给 Nginx 或 CDN,Apache 只处理动态请求,可以显著降低 Apache 的并发压力。
  • 若必须由 Apache 承担全部流量,则应:开启 mod_deflate 压缩文本、开启 mod_expires 让浏览器缓存、开启 EnableSendfile、关闭 HostnameLookups、把 AllowOverride 设为 None。
  • Apache 做反向代理转发给后端应用:
LoadModule proxy_module          modules/mod_proxy.so
LoadModule proxy_http_module     modules/mod_proxy_http.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
<VirtualHost *:80>
    ServerName www.test.com

    # 静态资源本地处理
    DocumentRoot /www/static
    ProxyPass /static/ !

    # 动态请求转给后端 Tomcat
    ProxyPreserveHost On
    ProxyPass        /app/ http://127.0.0.1:8080/app/
    ProxyPassReverse /app/ http://127.0.0.1:8080/app/

    # 负载均衡到多个后端
    <Proxy balancer://appcluster>
        BalancerMember http://192.168.0.11:8080 loadfactor=1
        BalancerMember http://192.168.0.12:8080 loadfactor=1
        ProxySet lbmethod=byrequests
    </Proxy>
    ProxyPass        /api/ balancer://appcluster/api/
    ProxyPassReverse /api/ balancer://appcluster/api/
</VirtualHost>
setsebool -P httpd_can_network_connect on     # 反代时 SELinux 必开

关闭正向代理(防止服务器被当作开放代理滥用):确认配置中没有 ProxyRequests On,默认即为 Off。

6.2.5 ab 压测验证

详见 07-02-网络配置与远程管理.md

ab(ApacheBench)由 httpd-tools 包提供,用于压测验证调优效果。

# -n 总请求数,-c 并发数
ab -n 10000 -c 100 http://127.0.0.1/index.html

# 开启长连接压测(更接近浏览器真实行为)
ab -n 10000 -c 100 -k http://127.0.0.1/index.html

# 指定超时、带 Host 头压测虚拟主机
ab -n 5000 -c 50 -s 30 -H "Host: www.test.com" http://127.0.0.1/

# POST 压测(-p 指定请求体文件,-T 指定 Content-Type)
ab -n 1000 -c 20 -p post.txt -T "application/x-www-form-urlencoded" http://127.0.0.1/api.php

# 压测 HTTPS
ab -n 1000 -c 20 https://127.0.0.1/

关键输出解读:

Requests per second:    3521.47 [#/sec] (mean)     <- 吞吐量,越高越好
Time per request:       28.397 [ms] (mean)         <- 平均响应时间(并发下)
Failed requests:        0                          <- 失败请求数,必须为 0
Non-2xx responses:      0                          <- 非 2xx 响应
Transfer rate:          1024.55 [Kbytes/sec]
Percentage of the requests served within a certain time (ms)
  50%     25
  90%     42
  99%    120                                       <- 关注 P99 长尾延迟
 100%    350 (longest request)

压测方法建议:

  1. 压测客户端与被压服务器分开,避免争抢 CPU;ab 单进程本身可能成为瓶颈,大并发可改用 wrk、siege、jmeter。
  2. 逐步加压(并发 50 → 100 → 200 → 500),记录每一档的 RPS、P99、错误数,找到吞吐量拐点。
  3. 压测同时观察服务端:
# 观察 Apache worker 使用情况
watch -n1 'curl -s http://127.0.0.1/server-status?auto | grep -E "BusyWorkers|IdleWorkers|ReqPerSec"'

# 观察系统资源
top
vmstat 1
iostat -x 1
ss -s                                  # 连接状态统计
netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

# 观察错误日志
tail -f /var/log/httpd/error_log
  1. 每次只调一个参数,压测对比,避免多变量同时改动无法归因。

6.3 日志分析

日志位置:/var/log/httpd/access_log(访问日志)、/var/log/httpd/error_log(错误日志)。

LOG=/var/log/httpd/access_log

# 1. 总访问量(PV)
wc -l $LOG

# 2. 独立访客数(UV,按 IP 去重)
awk '{print $1}' $LOG | sort -u | wc -l

# 3. 访问量 Top 20 的 IP(配合封禁刷站 IP)
awk '{print $1}' $LOG | sort | uniq -c | sort -rn | head -20

# 4. 状态码分布
awk '{print $9}' $LOG | sort | uniq -c | sort -rn

# 5. 所有 404 请求及次数(找死链)
awk '$9==404 {print $7}' $LOG | sort | uniq -c | sort -rn | head -20

# 6. 所有 5xx 请求(找故障接口)
awk '$9~/^5/ {print $9, $7}' $LOG | sort | uniq -c | sort -rn | head -20

# 7. 访问量 Top 20 的 URL
awk '{print $7}' $LOG | sort | uniq -c | sort -rn | head -20

# 8. 来源页统计(combined 格式第 11 列)
awk -F'"' '{print $4}' $LOG | sort | uniq -c | sort -rn | head -20

# 9. User-Agent 统计(识别爬虫)
awk -F'"' '{print $6}' $LOG | sort | uniq -c | sort -rn | head -20

# 10. 按小时统计访问量(找流量高峰)
awk '{print substr($4,14,2)}' $LOG | sort | uniq -c | sort -k2n

# 11. 按分钟统计(定位突发流量的准确时刻)
awk '{print substr($4,2,17)}' $LOG | sort | uniq -c | sort -rn | head -20

# 12. 统计总流量(第 10 列为响应字节数)
awk '{sum+=$10} END {printf "总流量: %.2f GB\n", sum/1024/1024/1024}' $LOG

# 13. 消耗流量最多的 URL
awk '{a[$7]+=$10} END {for(i in a) print a[i]/1024/1024" MB", i}' $LOG | sort -rn | head -20

# 14. 慢请求分析(日志格式中需带 %D,微秒)
#     假设 %D 在第 11 列,找出耗时超过 1 秒(1000000 微秒)的请求
awk '$11>1000000 {print $11/1000000" s", $7}' $LOG | sort -rn | head -20

# 15. 平均响应时间
awk '{sum+=$11; n++} END {print "平均耗时(ms):", sum/n/1000}' $LOG

# 16. 单 IP 的所有请求(排查可疑访问)
grep "^192.168.0.100 " $LOG | awk '{print $7}' | sort | uniq -c | sort -rn

# 17. 某段时间内的日志(用 sed 截取时间范围)
sed -n '/22\/Aug\/2026:10:00/,/22\/Aug\/2026:11:00/p' $LOG | wc -l

# 18. 并发估算:统计同一秒内的请求数峰值
awk '{print $4}' $LOG | sort | uniq -c | sort -rn | head -5

# 19. 找出可能的扫描/攻击行为(大量 404 来自同一 IP)
awk '$9==404 {print $1}' $LOG | sort | uniq -c | sort -rn | head -10

# 20. 实时监控访问
tail -f $LOG
tail -f $LOG | grep -v -E '\.(css|js|png|jpg|gif|ico)'    # 过滤静态资源

错误日志分析:

ELOG=/var/log/httpd/error_log

# 按错误级别统计
awk -F'[][]' '{print $6}' $ELOG | sort | uniq -c | sort -rn

# 常见错误关键字
grep -i "MaxRequestWorkers" $ELOG          # 并发打满
grep -i "Permission denied" $ELOG          # 权限或 SELinux 问题
grep -i "File does not exist" $ELOG        # 404 的具体路径
grep -i "Premature end of script headers" $ELOG   # CGI/PHP 输出格式错误
grep -i "PHP Fatal error" $ELOG            # PHP 致命错误
grep -i "AH00558" $ELOG                    # ServerName 未设置告警
grep -i "AH01630" $ELOG                    # client denied by server configuration(403)

第三方日志分析工具:AWStats、GoAccess、Webalizer,或接入 ELK / Loki 做集中分析。

# GoAccess 实时分析(需 EPEL 源)
yum install epel-release goaccess -y
goaccess /var/log/httpd/access_log --log-format=COMBINED
goaccess /var/log/httpd/access_log --log-format=COMBINED -o /var/www/html/report.html

6.4 排错清单

6.4.1 总览表

现象 排查方向
服务启动失败 先跑 httpd -t 看语法;再看 systemctl status httpd -l 与 journalctl -xeu httpd;CentOS 6 看 /etc/init.d/httpd start 的直接输出
浏览器无法访问 检查服务状态、firewall-cmd --list-all(CentOS 6 看 iptables -L)、SELinux、监听端口
403 Forbidden 检查目录/文件权限、Require/Order 授权是否缺失、Options -Indexes 是否导致列目录被拒、SELinux 上下文(ls -Zd /www)
403 且配置看着没问题 是否混用了 Order/Allow/Deny 与 Require,删掉 2.2 旧语法
404 Not Found 检查 DocumentRoot、主页文件名是否在 DirectoryIndex 中、Alias 配置、重写规则
500 Internal Server Error 查看 /var/log/httpd/error_log,多为 CGI/PHP 报错或 .htaccess 语法错误
502 / 503 后端 php-fpm / Tomcat 是否存活、httpd_can_network_connect 是否开启、并发是否打满
修改配置未生效 是否执行了 reload/restart;是否改的不是生效的那份文件;浏览器缓存
虚拟主机 IP 访问异常 / 总是命中第一个 httpd -S 查看匹配顺序,检查 ServerName 与请求 Host 是否一致
HTTPS 无法访问 是否装了 mod_ssl、443 是否放行、证书与私钥是否匹配、证书是否过期
页面中文乱码 AddDefaultCharset 与网页实际编码是否一致;目录列表用 IndexOptions Charset=UTF-8
不清楚指令用法 安装 httpd-manual 后访问 http://IP/manual 查本地手册

通用排查三连:

httpd -t                                   # 语法
systemctl status httpd -l                  # 状态
tail -50 /var/log/httpd/error_log          # 日志
curl -I http://127.0.0.1                   # 查看响应头

6.4.2 启动失败:端口占用

systemctl start httpd
# Job for httpd.service failed... 
journalctl -xeu httpd
# (98)Address already in use: AH00072: make_sock: could not bind to address [::]:80
# no listening sockets available, shutting down

排查与处理:

# 找出占用 80 端口的进程
ss -ntlp | grep :80
netstat -ntlp | grep :80
lsof -i :80
fuser -n tcp 80

# 常见原因:nginx 也在跑,或上次的 httpd 没退干净
systemctl stop nginx
pkill httpd
kill -9 <PID>

# 或者改成别的端口
vim /etc/httpd/conf/httpd.conf     # Listen 8080

6.4.3 启动告警:ServerName / FQDN

启动时出现(CentOS 6):

Starting httpd: httpd: Could not reliably determine the server's fully qualified domain name, using localhost.localdomain for ServerName

或(CentOS 7 日志中):

AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using ::1. Set the 'ServerName' directive globally to suppress this message

原因:没有绑定主机名或未设置 ServerName。这只是告警,不影响服务启动,但应消除。

解决方法一:在 httpd.conf 中启用 ServerName(2.2 中约 276 行):

ServerName www.example.com:80
# 或
ServerName localhost:80

解决方法二:设置系统主机名。

# CentOS 6
hostname www.cluster.com
vim /etc/sysconfig/network
# HOSTNAME=www.cluster.com
vim /etc/hosts
# 10.0.0.61  www.cluster.com
service httpd restart

# CentOS 7
hostnamectl set-hostname www.cluster.com
echo "10.0.0.61  www.cluster.com" >> /etc/hosts
systemctl restart httpd

6.4.4 403 Forbidden 逐项排查

# 1. 看错误日志的具体原因
tail -20 /var/log/httpd/error_log
# client denied by server configuration  -> 授权问题(Require / Order)
# Permission denied                      -> 文件系统权限或 SELinux
# Directory index forbidden by Options directive -> 没有主页且未开 Indexes

# 2. 文件系统权限:apache 用户必须能"通过"路径上的每一级目录(x 权限)
namei -l /www/index.html
ls -ld / /www /www/index.html
sudo -u apache cat /www/index.html         # 直接模拟 apache 用户读取
sudo -u apache ls /www

# 修正权限
chown -R root:apache /www
find /www -type d -exec chmod 755 {} \;
find /www -type f -exec chmod 644 {} \;

# 3. SELinux 上下文
getenforce
ls -Zd /www
ls -Z /www/index.html
# 应为 httpd_sys_content_t
chcon -R -t httpd_sys_content_t /www
semanage fcontext -a -t httpd_sys_content_t "/www(/.*)?"
restorecon -Rv /www

# 临时验证是否是 SELinux 导致
setenforce 0        # 若此时能访问,则确定是 SELinux 问题,改回 setenforce 1 并修上下文
grep denied /var/log/audit/audit.log | tail
audit2why < /var/log/audit/audit.log | tail -20

# 4. 授权配置:确认目录有对应的 <Directory> 且已放行
httpd -t -D DUMP_VHOSTS
grep -rn "Directory" /etc/httpd/conf/httpd.conf /etc/httpd/conf.d/

# 5. 检查是否混用了两代语法(2.4 上最隐蔽的 403 原因)
grep -rn -E "Order|Allow from|Deny from" /etc/httpd/conf/ /etc/httpd/conf.d/
# 找到后整段替换为 Require 写法

# 6. 检查是否被 <Directory /> 的默认拒绝拦住(Alias 到根外目录时常见)
# 必须为 Alias 的目标目录单独写 <Directory> 授权

# 7. 没有主页文件 + 未开 Indexes 也会 403
ls -l /www/index.html
grep DirectoryIndex /etc/httpd/conf/httpd.conf

6.4.5 404 Not Found 排查

# 1. 错误日志会给出 Apache 实际去找的绝对路径,这是最快的线索
tail /var/log/httpd/error_log
# File does not exist: /var/www/html/test.html
# -> 说明 Apache 找的是 /var/www/html,而你以为是 /www

# 2. 确认生效的 DocumentRoot(注意虚拟主机会覆盖主配置)
grep -rn "DocumentRoot" /etc/httpd/conf/httpd.conf /etc/httpd/conf.d/
httpd -S                                   # 看清每个虚拟主机的 DocumentRoot

# 3. 文件是否真的存在、大小写是否一致(Linux 区分大小写)
ls -l /www/index.html
ls /www | grep -i index

# 4. 主页文件名是否在 DirectoryIndex 列表中
grep DirectoryIndex /etc/httpd/conf/httpd.conf
# 例如只写了 index.html,而实际文件是 index.php -> 访问目录时报 403/404

# 5. Alias 排查:URL 前缀与文件系统路径是否对应,结尾斜杠是否一致
grep -rn "Alias" /etc/httpd/conf/ /etc/httpd/conf.d/
# Alias /software /external/software  -> 访问 /software/ 时找的是 /external/software/
# 注意:Alias /soft/ "/data/soft/" 两端都带斜杠时,访问 /soft(不带斜杠)不会匹配

# 6. 重写规则是否把请求改到了不存在的路径
LogLevel warn rewrite:trace3               # 开启重写调试,看 error_log

# 7. 软链接 404:目标目录的 Options 是否有 FollowSymLinks
ls -l /www/etc

6.4.6 500 Internal Server Error 排查

# 1. 错误日志是唯一可靠入口
tail -50 /var/log/httpd/error_log

# 2. CGI 常见原因
#    a) 脚本无执行权限
chmod 755 /var/www/cgi-bin/hello.cgi
#    b) 未输出 Content-type 头或缺少空行 -> Premature end of script headers
#    c) 脚本首行 shebang 路径错误或有 Windows 换行符(^M)
head -1 /var/www/cgi-bin/hello.cgi | cat -A
dos2unix /var/www/cgi-bin/hello.cgi
sed -i 's/\r$//' /var/www/cgi-bin/hello.cgi
#    d) 目录未开 ExecCGI 或未 AddHandler
#    e) 手动执行脚本看是否报错
sudo -u apache /var/www/cgi-bin/hello.cgi

# 3. PHP 常见原因
#    a) 打开 PHP 错误显示(仅排障期间)
vim /etc/php.ini
# display_errors = On
# log_errors = On
# error_log = /var/log/php_errors.log
systemctl restart httpd
tail -f /var/log/php_errors.log

#    b) 语法检查
php -l /www/index.php

#    c) 缺少扩展(连不上数据库、gd 报错等)
php -m
yum install php-mysql php-gd php-mbstring -y

#    d) php-fpm 模式下看 fpm 日志
tail -f /var/log/php-fpm/www-error.log
systemctl status php-fpm

# 4. .htaccess 语法错误也会导致 500
mv /www/.htaccess /tmp/          # 移走后能访问即可确认
# 常见原因:使用了 AllowOverride 不允许的指令,或模块未加载(如未加载 mod_rewrite 就写 RewriteEngine)

# 5. 排障结束后务必关闭错误显示
# display_errors = Off

6.4.7 虚拟主机总是命中第一个

现象:无论访问哪个域名,返回的都是第一个 <VirtualHost> 的内容。

原因与处理:

# 用 httpd -S 看清全局
httpd -S
原因 说明与处理
域名解析没生效 客户端把域名解析到了别处或根本没解析,请求的 Host 头对不上任何 ServerName。用 ping 域名、nslookup、dig 确认;或用 curl -H "Host: xxx" 绕过解析验证配置本身是否正确
用 IP 直接访问 IP 访问不带匹配的 Host,必然落到该 IP:端口 的第一个虚拟主机(即默认站点),这是正常行为
缺少 NameVirtualHost(仅 httpd 2.2) 2.2 中必须写 NameVirtualHost *:80,否则名称虚拟主机不生效,全部落到第一个。2.4 不需要
ServerName 写错或漏写 每个虚拟主机必须有唯一且正确的 ServerName;漏写会继承主服务器的,导致无法区分
<VirtualHost> 的 IP:端口 与 Listen 不一致 例如 Listen 80 但写了 <VirtualHost *:8080>,该块永远不会被匹配
混用 * 与具体 IP 同一端口上既有 <VirtualHost *:80> 又有 <VirtualHost 10.0.0.2:80> 时匹配规则复杂,容易出错;应统一风格
配置文件未被加载 虚拟主机写在了 conf.d/ 下但文件后缀不是 .conf,或写在 httpd.conf 中已被 Include 覆盖
域名大小写/末尾点 ServerName 不区分大小写,但要注意别写成 aaa.test.com.
# 绕过 DNS 直接验证虚拟主机配置
curl -H "Host: aaa.test.com" http://10.0.0.2/
curl -H "Host: bbb.test.com" http://10.0.0.2/
curl --resolve bbb.test.com:80:10.0.0.2 http://bbb.test.com/

若希望"用 IP 或未知域名访问时返回一个提示页/拒绝",可显式定义一个默认虚拟主机放在最前面:

<VirtualHost *:80>
    ServerName default.local
    DocumentRoot /www/default
    <Location />
        Require all denied
    </Location>
</VirtualHost>

6.4.8 配置改了不生效

原因 检查方法
没有 reload/restart systemctl reload httpd 或 systemctl restart httpd;CentOS 6 用 /etc/init.d/httpd reload
改的文件没被加载 httpd -t -D DUMP_INCLUDES;确认文件在 conf.d/ 下且后缀为 .conf;检查 Include/IncludeOptional 路径
同一指令在后面被覆盖 grep -rn "指令名" /etc/httpd/conf/ /etc/httpd/conf.d/,Apache 中后出现的通常覆盖先出现的
指令写在了不生效的上下文 Listen、User、Group、MPM 参数、ServerTokens 等只能写在全局,写在 <VirtualHost> 内无效
ServerLimit / ThreadLimit 改了但没完整重启 这两个硬上限只在完整 restart 时读取,reload/graceful 不生效
虚拟主机中的设置覆盖了主配置 虚拟主机内的 DocumentRoot、日志、Require 优先于主服务器段
<Location> 覆盖了 <Directory> 合并顺序中 <Location> 最后应用,优先级最高(见 3.6)
.htaccess 未生效 所在目录 AllowOverride 是否为 None;文件名是否与 AccessFileName 一致;文件权限 apache 是否可读
.htaccess 覆盖了主配置 .htaccess 会覆盖 <Directory> 中允许被覆盖的部分,检查目录下是否存在遗留的 .htaccess
浏览器缓存 强制刷新(Ctrl+F5)、隐私窗口,或直接用 curl -I 验证;Basic 认证凭据也会被浏览器缓存
客户端 DNS 缓存 ipconfig /flushdns(Windows)、systemctl restart nscd
存在多个 httpd 实例 ps -ef | grep httpd,源码安装与 RPM 安装同时存在时容易改错配置;httpd -V 确认 SERVER_CONFIG_FILE
# 确认当前进程实际使用的配置文件
httpd -V | grep -E "HTTPD_ROOT|SERVER_CONFIG_FILE"
ps -ef | grep httpd
which httpd

# 查看所有被加载的配置文件
httpd -t -D DUMP_INCLUDES

6.5 附:典型生产环境访问链路

        客户端
          |
        路由器
          |
        防火墙
          |
    反向代理 / 缓存(Nginx / CDN / Varnish)
          |
        网站(Apache / Tomcat / php-fpm)
          |
        数据库 / 缓存(MySQL / Redis)

七、练习

练习 1:设置简单的网页

  1. 清空防火墙规则并关闭 SELinux:

    # CentOS 6
    sudo iptables -F
    sudo setenforce 0
    
    # CentOS 7(推荐放行而非关闭)
    sudo firewall-cmd --add-service=http --permanent
    sudo firewall-cmd --reload
  2. 在 /var/www/html 中创建文件 index.html,并添加内容。

  3. 重启服务:

    sudo systemctl restart httpd
  4. 通过浏览器访问 http://127.0.0.1 或 http://192.168.0.* 测试。

练习 2:自定义 httpd 根目录

  1. 备份主配置文件:

    sudo cp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.bak
  2. 编辑主配置文件 /etc/httpd/conf/httpd.conf,修改以下内容:

    DocumentRoot "/www"
    <Directory "/www">
        Options Indexes
        Require all granted
    </Directory>

    httpd 2.2 对应写法:

    DocumentRoot "/www"
    <Directory "/www">
        Options Indexes
        Order allow,deny
        Allow from all
    </Directory>
  3. 创建目录并修改所有者:

    sudo mkdir /www
    sudo chown apache:apache /www
    sudo chcon -R -t httpd_sys_content_t /www      # SELinux 开启时
  4. 在 /www 下创建 index.html 并添加内容。

  5. 重启服务:

    sudo systemctl restart httpd
  6. 通过 http://127.0.0.1 或 http://192.168.0.* 测试。

练习 3:httpd 文件共享

要求:

  1. 把网站家目录设置为 /www。
  2. 清空 /www 目录下原有内容。
  3. 允许列出网站根目录中的内容。
  4. 不允许列出网站根目录下子目录 secret 的内容。
  5. 不允许访问所有目录下的 .jpg 文件,但 images 目录除外。
  6. 在 /www 目录下创建 1_root.jpg、2_root.jpg、3_root.jpg、4_root.jpg。
  7. 在 /www/images 目录下创建 1_image.jpg、2_image.jpg、3_image.jpg、4_image.jpg。
  8. 在 /www/secret 目录下创建一些文件。

参考配置:

DocumentRoot "/www"

<Files ~ "\.jpg$">
    Order allow,deny
    Require all denied
</Files>

<Directory "/www">
    Options Indexes
    Order deny,allow
    Require all granted
</Directory>

<Directory "/www/secret">
    Options -Indexes
</Directory>

<Directory "/www/images">
    <Files ~ "\.jpg$">
        Order deny,allow
        Require all granted
    </Files>
</Directory>

上述配置来自原始资料,其中同时出现了 Order 与 Require。在 httpd 2.4 上应统一为 Require:

DocumentRoot "/www"

<FilesMatch "\.jpg$">
    Require all denied
</FilesMatch>

<Directory "/www">
    Options Indexes
    Require all granted
</Directory>

<Directory "/www/secret">
    Options -Indexes
</Directory>

<Directory "/www/images">
    <FilesMatch "\.jpg$">
        Require all granted
    </FilesMatch>
</Directory>

补充说明:如果访问根目录时仍跳转到 CentOS 欢迎页,可编辑 /etc/httpd/conf.d/welcome.conf,将 Options -Indexes 中的 - 去掉,改为 Options Indexes:

<LocationMatch "^/+$">
    Options Indexes
    ErrorDocument 403 /.noindex.html
</LocationMatch>

练习 4:用密码保护 /www/books 路径

  1. 在配置文件中添加:

    <Directory "/www/books">
        Options Indexes FollowSymLinks
        Order allow,deny
        Allow from all
        AuthName "Authenticate yourself"
        AuthType basic
        AuthUserFile /etc/httpd/userpasswd
        Require valid-user
    </Directory>

    httpd 2.4 写法:

    <Directory "/www/books">
        Options Indexes FollowSymLinks
        AuthName "Authenticate yourself"
        AuthType Basic
        AuthUserFile /etc/httpd/userpasswd
        Require valid-user
    </Directory>
  2. 创建用户:

    htpasswd -c /etc/httpd/userpasswd u1
  3. 重启 httpd 服务:

    sudo systemctl restart httpd

练习 5:操练虚拟主机配置

要求:

  1. 配置两个虚拟主机,域名分别为 web1.abc.com 和 web2.abc.com。
  2. 文档根目录分别为 /www/web1 和 /www/web2,要求其中的 index.html 显示不同内容。
  3. 通过 DNS 服务器解析两个域名。

参考步骤:

  1. 本方案基于以下假设:

    • 虚拟主机 web1.abc.com 的文档根目录:/www/web1
    • 虚拟主机 web2.abc.com 的文档根目录:/www/web2
  2. 配置 DNS,设定以下域名与 IP 的映射:

    127.0.0.1   web1.abc.com
    127.0.0.1   web2.abc.com
  3. 在客户端配置 /etc/resolv.conf,然后测试 DNS 是否可以正常解析,并确认主机能够连通:

    # 编辑 /etc/resolv.conf,删除原有内容,添加以下一行(DNS 服务器地址按实际填写)
    nameserver 192.168.0.*
    
    # 测试解析
    dig web1.abc.com
    ping web1.abc.com
  4. 编辑 httpd 的子配置文件 /etc/httpd/conf.d/vir.conf,添加以下内容:

    <VirtualHost *:80>
        DocumentRoot /www/web1
        ServerName web1.abc.com
        <Location />
            Options Indexes
            Order deny,allow
            Allow from all
        </Location>
    </VirtualHost>
    
    <VirtualHost *:80>
        DocumentRoot /www/web2
        ServerName web2.abc.com
        <Location />
            Options Indexes
            Order deny,allow
            Allow from all
        </Location>
    </VirtualHost>

    httpd 2.4 推荐写法:

    <VirtualHost *:80>
        DocumentRoot /www/web1
        ServerName web1.abc.com
        <Directory "/www/web1">
            Options Indexes
            Require all granted
        </Directory>
    </VirtualHost>
    
    <VirtualHost *:80>
        DocumentRoot /www/web2
        ServerName web2.abc.com
        <Directory "/www/web2">
            Options Indexes
            Require all granted
        </Directory>
    </VirtualHost>
  5. 在两个虚拟主机的文档根目录下分别创建 index.html,内容如下:

    /www/web1/index.html:

    <h2>Web1</h2>

    /www/web2/index.html:

    <h2>Web2</h2>
  6. 重启 httpd 服务器:

    sudo systemctl restart httpd
  7. 在客户端分别访问以下网址,查看内容是否不同:

    http://web1.abc.com
    http://web2.abc.com

练习 6:Directory 容器子目录访问控制

在 /www 下新建 bbs 目录及 index.html,使用 <Directory> 容器控制该子目录仅允许自己的 IP 访问,其它 IP 全部拒绝。

mkdir /www/bbs
echo 'bbs page' > /www/bbs/index.html

httpd 2.2:

<Directory "/www/bbs">
    Order deny,allow
    Deny from all
    Allow from 10.0.0.2
</Directory>

httpd 2.4:

<Directory "/www/bbs">
    Require ip 10.0.0.2
</Directory>

<Directory> 容器不能嵌套,子目录需写绝对路径、单独定义。

练习 7:以目录列表形式显示文件

有两种方式:

  1. 删除或重命名 /etc/httpd/conf.d/welcome.conf。

  2. 在网站根目录下创建一个名为 index.html 的目录(让 Apache 找不到同名主页文件):

    mkdir /var/www/html/index.html

然后浏览器访问服务器 IP,即可看到目录列表。

练习 8:日志轮转

需求:对 Apache 日志做每天一次的轮转,分别用 logrotate、自定义脚本、cronolog 三种方式实现(见 5.7 节)。

练习 9:源码编译安装

下载最新版本的 httpd 源码包,进行编译安装(区别于 RPM 安装),熟悉源码编译的三步:./configure、make、make install(见 2.2 节)。要求:

  1. 安装到 /usr/local/apache2。
  2. 启用 so、rewrite、ssl 模块支持。
  3. 把源码安装的 httpd 纳入 PATH 与 systemd(或 SysV)管理。
  4. 与已有的 RPM 版 httpd 使用不同端口,两者互不冲突。

练习 10:综合实验

要求:

  1. 关闭 iptables,关闭 SELinux。

  2. 把文件系统目录 /www 设为网站根目录。

  3. 把网站默认主页名称设为 index.html。

  4. 网站根目录允许列目录功能(Indexes)。

  5. 在网站根目录下创建文件 main.do,内容为 main page(不包含双引号)。

  6. 在网站根目录下创建目录 special,只允许 IP 为 192.168.0.223 和本机 127.0.0.1 访问。

  7. 在网站根目录下创建空文件 special/data。

  8. 网站中所有(除第 9 步中目录外)扩展名为 .jpg、.png 的文件,禁止所有人访问。

  9. 在网站根目录下创建目录 images,其中扩展名为 .png 的文件允许所有人访问。

  10. 在网站根目录下创建以下空文件:

     file.jpg
     file.png
     file.bmp
     images/file.jpg
     images/file.png
  11. 在文件系统上创建目录 /www_admin,用别名的方式把该目录映射成网站路径 /admin,该路径允许列目录功能。

  12. 在文件系统上创建空文件 /www_admin/data。

  13. 在网站根目录下创建目录 secret,其中的内容需要提供用户名和密码才能访问,用户名和密码都设置为 abc。

  14. 在文件系统上创建文件 /www/secret/data,内容为 secret message(不包含双引号)。

  15. 在网站根目录下创建一个软链接,指向系统目录 /etc,使得可以通过网址 http://192.168.0.*/etc/fstab 的形式访问文件 /etc/fstab,/etc 下的其他文件不允许访问。

预期效果(假设服务器 IP 地址为 192.168.0.183):

  1. 访问 http://192.168.0.183 能列出网站根目录,看到文件 main.do。
  2. 访问 http://192.168.0.183/main.do 能看到内容 main page。
  3. 只有 192.168.0.220 和 192.168.0.183 才能访问 http://192.168.0.183/special。
  4. 访问 http://192.168.0.183 看到的图片文件只有 file.bmp。
  5. 访问 http://192.168.0.183/images 看到的图片文件只有 file.png。
  6. 访问 http://192.168.0.183/admin/ 能看到文件列表,里面有文件 data。
  7. 访问 http://192.168.0.183/secret/data 需要用户名和密码,输入用户名 abc 和密码 abc 后能看到内容 secret message。
  8. 可以访问 http://192.168.0.183/etc/fstab。
  9. 不能访问 http://192.168.0.183/etc/passwd。

参考配置(只包含修改/增加的部分,httpd 2.2 语法):

DocumentRoot "/www"

<Directory "/www">
    Options Indexes
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>

DirectoryIndex index.html

<Directory "/www/special">
    Order deny,allow
    Deny from all
    Allow from 192.168.0.183 127.0.0.1
</Directory>

<Files ~ "\.(jpg|png)$">
    Order deny,allow
    Deny from all
</Files>

<Directory "/www/images">
    <Files ~ "\.png$">
        Order deny,allow
        Allow from all
    </Files>
</Directory>

Alias /admin "/www_admin"

<Directory "/www_admin">
    Options Indexes
    Order deny,allow
    Allow from all
</Directory>

# 除了以下配置之外,还需要创建用户
# 可以使用以下命令创建用户:
# htpasswd -c /etc/httpd/userpasswd abc

<Directory "/www/secret">
    AuthName "Authenticate yourself"
    AuthType basic
    AuthUserFile /etc/httpd/userpasswd
    Require valid-user
</Directory>

<Directory "/www/etc">
    Order allow,deny
    Deny from all
    <Files ~ "^fstab$">
        Order deny,allow
        Allow from all
    </Files>
</Directory>

参考配置(httpd 2.4 语法):

DocumentRoot "/www"
DirectoryIndex index.html

<Directory "/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

<Directory "/www/special">
    Require ip 192.168.0.223 127.0.0.1
</Directory>

<FilesMatch "\.(jpg|png)$">
    Require all denied
</FilesMatch>

<Directory "/www/images">
    <FilesMatch "\.png$">
        Require all granted
    </FilesMatch>
</Directory>

Alias /admin "/www_admin"

<Directory "/www_admin">
    Options Indexes
    Require all granted
</Directory>

<Directory "/www/secret">
    AuthName "Authenticate yourself"
    AuthType Basic
    AuthUserFile /etc/httpd/userpasswd
    Require valid-user
</Directory>

<Directory "/www/etc">
    Options FollowSymLinks
    Require all denied
    <Files "fstab">
        Require all granted
    </Files>
</Directory>

准备命令:

mkdir -p /www/{special,images,secret} /www_admin
echo 'main page' > /www/main.do
touch /www/special/data /www_admin/data
touch /www/file.jpg /www/file.png /www/file.bmp
touch /www/images/file.jpg /www/images/file.png
echo 'secret message' > /www/secret/data
ln -s /etc /www/etc
htpasswd -c /etc/httpd/userpasswd abc
chown -R apache:apache /www /www_admin
httpd -t
systemctl restart httpd

--------- end of 综合实验 ---------


八、关键配置与命令速查

8.1 关键配置速查

配置项 默认值/常用值 说明
ServerRoot /etc/httpd 基础目录
Listen 80 监听端口
DocumentRoot /var/www/html 网站根目录
DirectoryIndex index.html 默认主页
ServerTokens Full(建议改 Prod) 服务器版本显示级别
ServerSignature On(建议改 Off) 错误页版本签名
Timeout 60(建议 30) 连接超时
KeepAlive Off(建议 On) 长连接开关
KeepAliveTimeout 15(建议 2~5) 长连接空闲超时
MaxRequestWorkers 依 MPM 而定 最大并发处理数(2.2 为 MaxClients)
AllowOverride None 是否启用 .htaccess
Options Indexes FollowSymLinks(建议 -Indexes) 目录特性
ErrorLog logs/error_log 错误日志
CustomLog logs/access_log combined 访问日志
LogLevel warn 日志级别
HostnameLookups Off 反向 DNS 解析(务必保持 Off)
EnableSendfile on 零拷贝发送文件
Order allow,deny + Allow from all — 2.2 放行写法
Require all granted — 2.4 放行写法,替代上一行
SSLEngine on(ssl.conf 内) 启用 HTTPS

8.2 常用命令速查

命令 用途
yum install httpd httpd-tools httpd-manual mod_ssl -y 安装 httpd 及手册、SSL 模块
httpd -t / apachectl configtest 配置文件语法检查
httpd -v 查看版本号
httpd -V 查看版本、编译参数与当前 MPM
httpd -l 查看静态编译进 httpd 的模块
httpd -M 查看已加载模块
httpd -S 查看虚拟主机匹配情况
apachectl graceful 优雅重启,不中断请求
systemctl start/stop/reload/restart/enable httpd 服务管理(CentOS 7)
service httpd start / chkconfig httpd on 服务管理(CentOS 6)
ss -ntlp / netstat -ntlp 查看端口监听
htpasswd -c /etc/httpd/userpasswd u1 创建认证用户(追加时去掉 -c)
htdigest -c /etc/httpd/digestpasswd "realm" u1 创建 Digest 认证用户
firewall-cmd --add-service=http --permanent 放行 80 端口
firewall-cmd --add-service=https --permanent 放行 443 端口
firewall-cmd --add-port=8000/tcp --permanent 放行自定义端口
firewall-cmd --reload 重载防火墙规则
chcon -t httpd_sys_content_t /www -R 设置 SELinux 上下文(临时)
semanage fcontext -a -t httpd_sys_content_t "/www(/.*)?" 写入 SELinux 策略(永久)
restorecon -Rv /www 按策略恢复上下文
setsebool -P httpd_can_network_connect on 允许 httpd 发起外部连接
semanage port -a -t http_port_t -p tcp 8000 允许 httpd 监听非默认端口
openssl genrsa -out ca.key 2048 生成私钥
openssl req -new -key ca.key -out ca.csr 生成证书签名请求
openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt 生成自签名证书
openssl s_client -connect IP:443 查看 TLS 握手与证书
curl -I http://IP / curl -Ik https://IP 查看响应头
curl -H "Host: a.test.com" http://IP/ 绕过 DNS 测试虚拟主机
ab -n 10000 -c 100 http://IP/ 压力测试
elinks http://IP/ 命令行文本浏览器测试
logrotate -f /etc/logrotate.d/httpd 手动执行日志轮转
最后修改:2026 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏