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 | |
| 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) |
见第四章 |
匹配与合并顺序(后应用者优先级更高,会覆盖先前的设置):
<Directory>(不含正则)—— 按路径从短到长依次应用(父目录先、子目录后)<DirectoryMatch>与带正则的<Directory ~ >.htaccess(若AllowOverride允许,与所在目录的<Directory>一同生效,且会覆盖<Directory>中允许被覆盖的部分)<Files>与<FilesMatch><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,denyAllow from all |
Require all granted |
| 拒绝所有 | Order allow,denyDeny from all |
Require all denied |
| 只允许某 IP | Order deny,allowDeny from allAllow from 1.1.1.3 |
Require ip 1.1.1.3 |
| 允许所有但排除某 IP | Order allow,denyAllow from allDeny from 1.1.1.3 |
<RequireAll>Require all grantedRequire not ip 1.1.1.3</RequireAll> |
| 只允许本机 | Order deny,allowDeny from allAllow 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 负责在目录中没有主页文件时生成目录列表。
满足以下条件时,目录会以列表形式展示:
- 该目录对应的
Options中包含Indexes; - 该目录下没有
DirectoryIndex指定的主页文件; - 注释或删除
/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-runLet'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-statushttp://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)
压测方法建议:
- 压测客户端与被压服务器分开,避免争抢 CPU;
ab单进程本身可能成为瓶颈,大并发可改用wrk、siege、jmeter。 - 逐步加压(并发 50 → 100 → 200 → 500),记录每一档的 RPS、P99、错误数,找到吞吐量拐点。
- 压测同时观察服务端:
# 观察 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
- 每次只调一个参数,压测对比,避免多变量同时改动无法归因。
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:设置简单的网页
-
清空防火墙规则并关闭 SELinux:
# CentOS 6 sudo iptables -F sudo setenforce 0 # CentOS 7(推荐放行而非关闭) sudo firewall-cmd --add-service=http --permanent sudo firewall-cmd --reload -
在
/var/www/html中创建文件index.html,并添加内容。 -
重启服务:
sudo systemctl restart httpd -
通过浏览器访问
http://127.0.0.1或http://192.168.0.*测试。
练习 2:自定义 httpd 根目录
-
备份主配置文件:
sudo cp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.bak -
编辑主配置文件
/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> -
创建目录并修改所有者:
sudo mkdir /www sudo chown apache:apache /www sudo chcon -R -t httpd_sys_content_t /www # SELinux 开启时 -
在
/www下创建index.html并添加内容。 -
重启服务:
sudo systemctl restart httpd -
通过
http://127.0.0.1或http://192.168.0.*测试。
练习 3:httpd 文件共享
要求:
- 把网站家目录设置为
/www。 - 清空
/www目录下原有内容。 - 允许列出网站根目录中的内容。
- 不允许列出网站根目录下子目录
secret的内容。 - 不允许访问所有目录下的
.jpg文件,但images目录除外。 - 在
/www目录下创建1_root.jpg、2_root.jpg、3_root.jpg、4_root.jpg。 - 在
/www/images目录下创建1_image.jpg、2_image.jpg、3_image.jpg、4_image.jpg。 - 在
/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 路径
-
在配置文件中添加:
<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> -
创建用户:
htpasswd -c /etc/httpd/userpasswd u1 -
重启 httpd 服务:
sudo systemctl restart httpd
练习 5:操练虚拟主机配置
要求:
- 配置两个虚拟主机,域名分别为
web1.abc.com和web2.abc.com。 - 文档根目录分别为
/www/web1和/www/web2,要求其中的index.html显示不同内容。 - 通过 DNS 服务器解析两个域名。
参考步骤:
-
本方案基于以下假设:
- 虚拟主机
web1.abc.com的文档根目录:/www/web1 - 虚拟主机
web2.abc.com的文档根目录:/www/web2
- 虚拟主机
-
配置 DNS,设定以下域名与 IP 的映射:
127.0.0.1 web1.abc.com 127.0.0.1 web2.abc.com -
在客户端配置
/etc/resolv.conf,然后测试 DNS 是否可以正常解析,并确认主机能够连通:# 编辑 /etc/resolv.conf,删除原有内容,添加以下一行(DNS 服务器地址按实际填写) nameserver 192.168.0.* # 测试解析 dig web1.abc.com ping web1.abc.com -
编辑 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> -
在两个虚拟主机的文档根目录下分别创建
index.html,内容如下:/www/web1/index.html:<h2>Web1</h2>/www/web2/index.html:<h2>Web2</h2> -
重启 httpd 服务器:
sudo systemctl restart httpd -
在客户端分别访问以下网址,查看内容是否不同:
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:以目录列表形式显示文件
有两种方式:
-
删除或重命名
/etc/httpd/conf.d/welcome.conf。 -
在网站根目录下创建一个名为
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 节)。要求:
- 安装到
/usr/local/apache2。 - 启用 so、rewrite、ssl 模块支持。
- 把源码安装的 httpd 纳入 PATH 与 systemd(或 SysV)管理。
- 与已有的 RPM 版 httpd 使用不同端口,两者互不冲突。
练习 10:综合实验
要求:
-
关闭 iptables,关闭 SELinux。
-
把文件系统目录
/www设为网站根目录。 -
把网站默认主页名称设为
index.html。 -
网站根目录允许列目录功能(Indexes)。
-
在网站根目录下创建文件
main.do,内容为main page(不包含双引号)。 -
在网站根目录下创建目录
special,只允许 IP 为192.168.0.223和本机127.0.0.1访问。 -
在网站根目录下创建空文件
special/data。 -
网站中所有(除第 9 步中目录外)扩展名为
.jpg、.png的文件,禁止所有人访问。 -
在网站根目录下创建目录
images,其中扩展名为.png的文件允许所有人访问。 -
在网站根目录下创建以下空文件:
file.jpg file.png file.bmp images/file.jpg images/file.png -
在文件系统上创建目录
/www_admin,用别名的方式把该目录映射成网站路径/admin,该路径允许列目录功能。 -
在文件系统上创建空文件
/www_admin/data。 -
在网站根目录下创建目录
secret,其中的内容需要提供用户名和密码才能访问,用户名和密码都设置为abc。 -
在文件系统上创建文件
/www/secret/data,内容为secret message(不包含双引号)。 -
在网站根目录下创建一个软链接,指向系统目录
/etc,使得可以通过网址http://192.168.0.*/etc/fstab的形式访问文件/etc/fstab,/etc下的其他文件不允许访问。
预期效果(假设服务器 IP 地址为 192.168.0.183):
- 访问
http://192.168.0.183能列出网站根目录,看到文件main.do。 - 访问
http://192.168.0.183/main.do能看到内容main page。 - 只有
192.168.0.220和192.168.0.183才能访问http://192.168.0.183/special。 - 访问
http://192.168.0.183看到的图片文件只有file.bmp。 - 访问
http://192.168.0.183/images看到的图片文件只有file.png。 - 访问
http://192.168.0.183/admin/能看到文件列表,里面有文件data。 - 访问
http://192.168.0.183/secret/data需要用户名和密码,输入用户名abc和密码abc后能看到内容secret message。 - 可以访问
http://192.168.0.183/etc/fstab。 - 不能访问
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 |
手动执行日志轮转 |
此处评论已关闭