共计 12626 个字符,预计需要花费 32 分钟才能阅读完成。
一、两种架构的本质区别
1.1 wordpress:*-apache(当前方案)
客户端 → Nginx (反代) → WordPress 容器内 Apache (mod_php) → PHP 处理
│
└── Apache 既是 Web 服务器,又是 PHP 运行环境
PHP 作为 Apache 模块嵌入进程内部
- 单进程模型: Apache + mod_php 合为一体
- 请求处理: 每个 Apache worker 进程都加载完整的 PHP 解释器
- 配置简单: 一个容器搞定一切,无需额外组件
1.2 wordpress:*-fpm
客户端 → Nginx (反代) → WordPress 容器内 Nginx/FPM → php-fpm 进程池 → PHP 处理
│ │
└── 容器内仍需一个 Web 服务器 └── 独立的 PHP 进程池
(通常是 Nginx 或 Apache) 通过 Unix Socket / TCP 通信
⚠️ 关键认知纠正:
wordpress:*-fpm镜像不包含任何 Web 服务器。它只包含 PHP-FPM 和 WordPress 文件。你必须在同一个容器中再装一个 Web 服务器(Nginx/Apache),或者用外部 Nginx 直接连 FPM——但后者需要共享文件系统,复杂度更高。
实际上,Docker 官方 wordpress:*-fpm 镜像的设计意图是:搭配一个独立的 Nginx 容器使用,两个容器通过 volume 共享 WordPress 文件,通过 TCP/Socket 传递 PHP 请求。
┌─────────────────┐ ┌──────────────────────┐
│ Nginx 容器 │ TCP │ WordPress-FPM 容器 │
│ │◄───────►│ │
│ 静态文件 ←─────│ volume │ /var/www/html │
│ PHP 请求 ──────│────────►│ php-fpm :9000 │
└─────────────────┘ └──────────────────────┘
二、性能对比
2.1 内存占用
| 指标 | Apache (mod_php) | FPM (Nginx + php-fpm) |
|---|---|---|
| 空闲内存 | ~80-120MB | ~40-60MB |
| 每并发请求内存 | ~30-50MB(Apache worker + PHP) | ~15-25MB(仅 FPM worker) |
| 静态文件请求 | 经过 Apache + mod_php(浪费) | Nginx 直接返回(零 PHP 开销) |
| 100 并发峰值 | ~3-5GB | ~1.5-2.5GB |
FPM 优势的核心原因: 静态资源(CSS/JS/图片)由 Nginx 直接处理,完全不经过 PHP。而 Apache mod_php 模式下,即使返回一张图片,也要启动一个携带完整 PHP 解释器的 Apache worker。
2.2 并发能力
| 指标 | Apache (prefork) | FPM |
|---|---|---|
| 最大并发 | 受限于 Apache MaxRequestWorkers | Nginx 异步 + FPM 进程池独立扩展 |
| 静态文件并发 | 与 PHP 请求竞争 worker | 互不影响 |
| 长连接保持 | 每个连接占一个 worker | Nginx 单线程处理数千连接 |
| 突发流量 | worker 耗尽 → 排队/拒绝 | Nginx 缓冲 + FPM 队列 |
2.3 实际场景判断
| 场景 | 推荐 | 原因 |
|---|---|---|
| 个人博客,日均 PV < 5000 | Apache ✅ | 简单够用,差异不可感知 |
| 中型站点,日均 PV 5000-50000 | FPM ✅ | 内存节省明显,静态资源快 |
| 高流量/多站点 | FPM ✅✅ | 并发能力和资源效率优势巨大 |
| 服务器内存 ≤ 1GB | FPM ✅ | Apache mod_php 可能 OOM |
| 追求最低运维复杂度 | Apache ✅ | 少一个组件,少一套配置 |
对于本方案(个人博客): Apache 镜像完全胜任。FPM 的性能优势在小流量下几乎不可感知,但配置复杂度显著增加。选择取决于你的优先级:简单 vs 极致性能。
三、如果选用 FPM,需要改什么?
3.1 变更总览
| 组件 | Apache 方案(当前) | FPM 方案 |
|---|---|---|
| WordPress 镜像 | wordpress:7.0.2-php8.4-apache |
wordpress:7.0.2-php8.4-fpm |
| 容器内 Web 服务器 | Apache(内置) | ❌ 无(需外部 Nginx 处理) |
| Nginx 反代目标 | proxy_pass http://wordpress:80 |
fastcgi_pass wordpress:9000 |
| 静态文件 | 由 WordPress 容器的 Apache 提供 | 由 Nginx 容器直接读取共享 volume |
| 配置文件 | proxy-wordpress.conf |
fastcgi-wordpress.conf(全新) |
| Volume 共享 | 不需要 | Nginx 和 FPM 容器必须共享 /var/www/html |
| PHP 配置挂载 | 挂到 WordPress 容器 | 挂到 WordPress-FPM 容器(不变) |
3.2 docker-compose.yml 变更
name: wpsite
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
# ═══════════════════════════════════════════
# Nginx — 现在同时承担反代 + 静态文件服务
# ═══════════════════════════════════════════
nginx:
image: nginx:1.30.4-alpine
container_name: nginx
restart: unless-stopped
depends_on:
- wordpress
ports:
- "80:80/tcp"
- "443:443/tcp"
- "443:443/udp"
environment:
TZ: Asia/Shanghai
volumes:
- /web/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /web/nginx/conf.d:/etc/nginx/conf.d:ro
- /web/nginx/snippets:/etc/nginx/snippets:ro
# ★ 关键变更:Nginx 需要直接读取 WordPress 文件来服务静态资源
- /web/wp:/var/www/html:ro
- /web/ssl/fullchain.cer:/etc/ssl/fullchain.cer:ro
- /web/ssl/abc.com.key:/etc/ssl/abc.com.key:ro
# Basic Auth 密码文件
- /web/nginx/htpasswd:/etc/nginx/htpasswd:ro
# 日志(可写)
- /web/nginx/log:/var/log/nginx
# 穿透到宿主机(适用于后端程序直接装在宿主机的场景)
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- web
- admin
logging: *default-logging
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/nginx-health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
# ═══════════════════════════════════════════
# WordPress FPM — 不再包含 Web 服务器
# ═══════════════════════════════════════════
wordpress:
# ★ 关键变更:从 apache 改为 fpm
image: wordpress:7.0.2-php8.4-fpm
container_name: wp
restart: unless-stopped
depends_on:
mysql:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: mysql:3306
WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME}
WORDPRESS_DB_USER: ${WORDPRESS_DB_USER}
WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
TZ: Asia/Shanghai
WORDPRESS_CONFIG_EXTRA: |
define('DISALLOW_FILE_EDIT', true);
define('WP_AUTO_UPDATE_CORE', 'minor');
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
volumes:
# ★ 同一个宿主机目录,Nginx 以 ro 挂载读静态文件,
# FPM 以 rw 挂载执行 PHP 和写入上传文件
- /web/wp:/var/www/html
- /web/php/upload.ini:/usr/local/etc/php/conf.d/upload.ini:ro
networks:
- web
- backend
logging: *default-logging
# ★ FPM 容器不再暴露 HTTP 端口,只监听 9000(FastCGI)
# 不需要 ports 映射,Nginx 通过 web 网络访问 wordpress:9000
# MariaDB 和 phpMyAdmin 配置不变,此处省略
mysql:
image: mariadb:11.4
container_name: wpdb
hostname: mysql
restart: unless-stopped
environment:
MARIADB_DATABASE: ${WORDPRESS_DB_NAME}
MARIADB_USER: ${WORDPRESS_DB_USER}
MARIADB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
TZ: Asia/Shanghai
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-name-resolve
- --max_allowed_packet=256M
- --innodb_buffer_pool_size=256M
volumes:
- db_data:/var/lib/mysql
- /web/mysql/conf.d:/etc/mysql/conf.d:ro
- /web/mysql/log:/var/log/mysql
networks:
- backend
logging: *default-logging
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
dbweb:
image: phpmyadmin:5.2
container_name: dbadmin
restart: unless-stopped
# 可选:使用 profiles 按需启动
# 启用:docker compose --profile admin up -d
# 停止:docker compose --profile admin stop
# profiles:
# - admin
depends_on:
mysql:
condition: service_healthy
environment:
PMA_HOST: mysql
PMA_PORT: "3306"
UPLOAD_LIMIT: 100M
TZ: Asia/Shanghai
# 应急通道:仅监听本机 8098,通过 SSH 隧道访问
ports:
- "127.0.0.1:8098:80"
volumes:
- /web/php/upload.ini:/usr/local/etc/php/conf.d/upload.ini:ro
networks:
- backend
- admin
logging: *default-logging
networks:
web:
admin:
backend:
internal: true
volumes:
db_data:
3.3 nginx.conf 变更
upstream 定义需要删除(FPM 不走 HTTP upstream),改为 FastCGI 参数:
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
server_names_hash_bucket_size 64;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65s;
keepalive_requests 1000;
client_max_body_size 100m;
client_body_buffer_size 128k;
client_body_timeout 30s;
client_header_timeout 30s;
send_timeout 30s;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
limit_req_zone $binary_remote_addr zone=general:20m rate=50r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=admin:10m rate=10r/m;
limit_conn_zone $binary_remote_addr zone=addr:20m;
limit_req_status 429;
limit_conn_status 429;
map $http_upgrade $connection_upgrade {
default upgrade;
'' "";
}
# ─── Docker 内置 DNS ───
# 在纯 upstream 方案中,resolver 指令不是必需的。保留它是为了防御性配置,将来需要时无需临时添加。
resolver 127.0.0.11 valid=10s ipv6=off;
resolver_timeout 5s;
# ★ 关键变更:删除 upstream wordpress_backend
# FPM 使用 fastcgi_pass,不走 HTTP upstream
# phpMyAdmin 仍然是 HTTP 反代,保留其 upstream
upstream phpmyadmin_backend {
server dbweb:80 max_fails=3 fail_timeout=10s;
keepalive 8;
}
# ★ 新增:FastCGI 全局参数(所有 WordPress location 复用)
# 这些参数对应原来 proxy-wordpress.conf 中的 proxy_* 指令
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# FastCGI 缓冲区
fastcgi_buffer_size 16k;
fastcgi_buffers 8 16k;
fastcgi_busy_buffers_size 32k;
fastcgi_read_timeout 60s;
fastcgi_send_timeout 60s;
fastcgi_connect_timeout 10s;
include /etc/nginx/conf.d/*.conf;
}
3.4 新建 snippets/fastcgi-wordpress.conf(替代 proxy-wordpress.conf)
# WordPress FastCGI 公共配置(替代原来的 proxy-wordpress.conf)
# ★ 指向 FPM 容器的 9000 端口
fastcgi_pass wordpress:9000;
# WordPress 需要 index.php 作为入口
fastcgi_index index.php;
# 包含标准 FastCGI 参数
include fastcgi_params;
# ★ 关键:告诉 FPM 脚本文件的完整路径
# $document_root 来自 Nginx 的 root 指令
# $fastcgi_script_name 来自请求 URI
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 传递真实客户端信息给 WordPress
fastcgi_param HTTP_X_REAL_IP $remote_addr;
fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
fastcgi_param HTTP_X_FORWARDED_PROTO $scheme;
fastcgi_param HTTP_X_FORWARDED_HOST $host;
fastcgi_param HTTP_X_FORWARDED_PORT $server_port;
# 禁止 FPM 缓存 PHP 文件(开发时可开启提升性能)
# fastcgi_param PHP_VALUE "opcache.enable=0";
3.5 abc.com.conf 变更
核心变化:静态文件由 Nginx 直接服务,PHP 请求走 FastCGI。
# HTTP → HTTPS 跳转(不变)
server {
listen 80;
server_name abc.com www.abc.com;
location / {
return 301 https://abc.com$request_uri;
}
}
# www → 主域跳转(不变)
server {
listen 443 ssl;
http2 on;
server_name www.abc.com;
ssl_certificate /etc/ssl/fullchain.cer;
ssl_certificate_key /etc/ssl/abc.com.key;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
return 301 https://abc.com$request_uri;
}
}
# ═══════════════════════════════════════
# 主站:abc.com(FPM 版本)
# ═══════════════════════════════════════
server {
listen 443 ssl;
listen 443 quic reuseport;
http2 on;
server_name abc.com;
ssl_certificate /etc/ssl/fullchain.cer;
ssl_certificate_key /etc/ssl/abc.com.key;
# 安全响应头(不变)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
client_max_body_size 100m;
# ★ 关键变更:指定 WordPress 文件根目录
# Nginx 直接从共享 volume 读取静态文件
root /var/www/html;
index index.php index.html;
# 健康检查(不变)
location = /nginx-health {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
# 禁止 XML-RPC(不变)
location = /xmlrpc.php {
return 444;
}
# 禁止敏感文件(不变)
location ~* /(wp-config\.php|readme\.html|license\.txt|wp-config-sample\.php)$ {
return 404;
}
location ~* /\.(env|git|svn|htaccess|htpasswd|ini|log|sql|bak|old|save|swp)$ {
return 444;
}
location ~ /\. {
return 404;
}
# 禁止 uploads 目录执行 PHP(不变)
location ~* /wp-content/uploads/.*\.php$ {
return 403;
}
# ★ 新增:静态文件直接由 Nginx 返回,不经过 FPM
# 这是 FPM 架构性能优势的核心体现
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|avif|webp)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
# try_files 确保文件存在,不存在则返回 404
try_files $uri =404;
}
# WordPress 登录限流
location = /wp-login.php {
limit_req zone=login burst=5 nodelay;
limit_conn addr 8;
# ★ 从 proxy include 改为 fastcgi include
include /etc/nginx/snippets/fastcgi-wordpress.conf;
}
# admin-ajax 限流
location = /wp-admin/admin-ajax.php {
limit_req zone=general burst=50 nodelay;
limit_conn addr 30;
include /etc/nginx/snippets/fastcgi-wordpress.conf;
}
# ★ 主站请求:WordPress 伪静态规则 + FastCGI
location / {
limit_req zone=general burst=100 nodelay;
limit_conn addr 100;
# WordPress 永久链接支持
try_files $uri $uri/ /index.php?$args;
}
# ★ PHP 请求统一走 FastCGI
location ~ \.php$ {
limit_req zone=general burst=100 nodelay;
limit_conn addr 100;
# 防止任意 PHP 文件执行(只允许存在的文件)
try_files $uri =404;
include /etc/nginx/snippets/fastcgi-wordpress.conf;
}
}
3.6 default.conf 和 pma.abc.com.conf
- default.conf: 不需要改动(不涉及 WordPress 反代)
- pma.abc.com.conf: 不需要改动(phpMyAdmin 仍然使用 HTTP upstream 反代)
四、FPM 方案的注意事项与踩坑点
4.1 文件权限问题(最常见的坑)
# Nginx 容器以 nginx 用户(UID 101)运行
# FPM 容器以 www-data 用户(UID 33)运行
# 两者对 /web/wp 的读写权限可能冲突
# 解决方案:统一设置所有权
chown -R 33:33 /web/wp # FPM 需要写入上传文件
chmod -R 755 /web/wp
# Nginx 以 ro 挂载,只需要读权限,不受影响
4.2 .htaccess 不再生效
Apache 镜像中,WordPress 的伪静态规则写在 .htaccess 里。FPM + Nginx 方案中,.htaccess 完全被忽略。所有重写规则必须在 Nginx 配置中手动实现:
# 这就是 .htaccess 中 RewriteRule 的 Nginx 等价写法
location / {
try_files $uri $uri/ /index.php?$args;
}
如果你安装了依赖 .htaccess 的插件(如某些缓存插件、安全插件),需要在 Nginx 中手动转换规则。
4.3 SCRIPT_FILENAME 必须正确
这是 FPM 方案中最容易出错的配置:
# ✅ 正确
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# ❌ 错误:缺少 $document_root
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
# → FPM 找不到文件 → 500 Internal Server Error
# ❌ 错误:硬编码路径
fastcgi_param SCRIPT_FILENAME /var/www/html/index.php;
# → 所有请求都执行 index.php → 页面全部相同
4.4 静态文件缓存策略
FPM 方案的一大优势是 Nginx 可以直接控制静态文件缓存。建议根据文件类型设置不同的过期时间:
# 字体和图片:长期缓存
location ~* \.(woff|woff2|ttf|eot|avif|webp|ico)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# CSS/JS:中等缓存(带版本号的文件可以更长)
location ~* \.(css|js)$ {
expires 30d;
add_header Cache-Control "public";
}
# 用户上传的图片
location ~* /wp-content/uploads/.*\.(jpg|jpeg|png|gif|svg)$ {
expires 90d;
add_header Cache-Control "public";
}
五、决策建议
你的站点日均 PV 是多少?
│
├── < 5000 ──→ Apache 镜像 ✅(当前方案,简单可靠)
│
├── 5000 ~ 50000 ──→ 考虑 FPM
│ └── 你能接受额外的配置复杂度吗?
│ ├── 能 → FPM ✅
│ └── 不能 → Apache + OPcache 也能撑住
│
└── > 50000 ──→ FPM ✅✅(几乎是必选项)
对于本方案的个人博客场景,Apache 镜像是更务实的选择。 FPM 的性能优势在小流量下无法体现,但配置复杂度和排障成本是实实在在的。如果未来流量增长到需要 FPM 的程度,再按本文第三节的指南迁移即可——核心改动就是三件事:换镜像、加 root、把 proxy_pass 换成 fastcgi_pass。
本文内容在完整版教程 Docker WordPress 建站指南:从零到生产级部署 基础上进行讨论