在 Linux 上使用 Apache 和 Nginx PHP 开发者入门指南

如果你靠写 PHP 吃饭(或者刚入门),迟早会跟 Web 服务器打交道。在 Linux 上,这通常意味着 Apache 或 Nginx。两者都非常成熟稳定,也都能很好地跑你的 PHP 应用——但它们的设计理念不同,也各自有一些「坑点」。

这篇指南面向 PHP 开发者,按步骤来,比较偏实战。我们会一起完成这些事情:安装整套环境、配好虚拟主机(在 Nginx 里叫 server block)、接上 PHP-FPM、设置合理的性能和安全默认值、开启 TLS,以及排查那些一定会碰到的诡异问题。你可以把它当成部署时的检查清单。

Apache vs Nginx

Apache 是模块驱动(module-based)的,使用 MPM(Multi-Processing Modules,多进程模块)来处理请求:

mpm_prefork + mod_php
每个进程只处理一个请求,并且直接把 PHP 嵌入进 Apache 进程。简单粗暴,但非常吃内存,扩展性差。新部署不推荐再用这一套。

mpm_event(或 worker)+ 通过 proxy_fcgi 使用 PHP-FPM
现代方式:Apache 高效处理 HTTP,把 PHP 执行交给 PHP-FPM。相比 mod_php 更好扩展。

Nginx 是事件驱动、异步架构。它完全不会直接运行 PHP,只会把 PHP 请求转发到 PHP-FPM。它在下面这些场景里很强:提供静态资源、作为反向代理、在高并发低内存场景下保持性能稳定。

到了 2025 年,大部分 PHP 团队不管选 Apache 还是 Nginx,都会用 PHP-FPM。

简单判断:

  • 如果你大量依赖 .htaccess(老项目或需要目录级别 override),Apache 会更顺手。
  • 如果你想要高性能的反向代理和简单直接的扩展能力,Nginx 更合适。
  • 你也可以让 Nginx 跑在 Apache 前面:Nginx 负责 TLS、静态文件和缓存;Apache(通常监听 :8080)跑带 .htaccess 的老应用。

假设环境说明

  • 示例基于 Ubuntu/Debian 和 RHEL/Alma/Rocky,其他发行版按包名自行调整。
  • 你有 sudo 权限。
  • PHP 版本假设为 8.2 或 8.3,按需替换版本号即可。
  • 网站目录统一使用 /var/www/,Apache 和 Nginx 都会用到。

小建议:保持系统精简。没在用的 Web 服务器干脆卸掉,避免占用 :80/:443 端口导致冲突。

基础环境安装

Ubuntu / Debian

bash
# 更新软件源
sudo apt update

# Apache 相关(现代方案:event MPM + PHP-FPM)
sudo apt install -y apache2 libapache2-mod-fcgid \
  php8.2-fpm php8.2-cli php8.2-mbstring php8.2-xml php8.2-curl php8.2-mysql php8.2-opcache

# Nginx 相关
sudo apt install -y nginx php8.2-fpm

# 常用工具
sudo apt install -y unzip curl ufw certbot python3-certbot-apache python3-certbot-nginx

RHEL / Alma / Rocky (8/9+)

bash
# PHP 及扩展
sudo dnf install -y @php:8.2 php-fpm php-cli php-mbstring php-xml php-curl php-mysqlnd php-opcache

# Web 服务器
sudo dnf install -y httpd mod_fcgid
sudo dnf install -y nginx certbot python3-certbot-apache python3-certbot-nginx

# 启动并设置开机自启
sudo systemctl enable --now php-fpm
sudo systemctl enable --now httpd
sudo systemctl enable --now nginx

路径约定:

  • Ubuntu Apache → /etc/apache2/,Nginx → /etc/nginx/
  • RHEL Apache → /etc/httpd/,Nginx → /etc/nginx/

PHP-FPM:共同的核心组件

PHP-FPM 负责以进程池的形式运行你的 PHP 代码。

bash
# 查看状态
sudo systemctl status php8.2-fpm     # Ubuntu/Debian
sudo systemctl status php-fpm        # RHEL 系

Socket vs TCP

  • Unix Socket/run/php/php8.2-fpm.sock(速度快,Ubuntu/Debian 默认)
  • TCP127.0.0.1:9000(适合 chroot/容器之间或多主机之间调用)

进程池配置

  • Ubuntu:/etc/php/8.2/fpm/pool.d/www.conf
  • RHEL:/etc/php-fpm.d/www.conf

示例配置(节选):

ini
user = www-data          ; Ubuntu 上通常是 www-data,RHEL 是 apache/nginx
group = www-data
listen = /run/php/php8.2-fpm.sock    ; 或 127.0.0.1:9000

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 8

粗略经验:

pm.max_children ≈ (给 PHP 预留的内存总量) / (单个 PHP 进程平均占用内存 MB)
ps/top/htop 实际观察进程内存占用再调。

变更后重载:

bash
sudo systemctl reload php8.2-fpm     # 或 php-fpm

安全/隔离建议:每个应用单独一个 PHP-FPM 进程池(不同的 listen socket 和 user),避免一个站拖垮其它站。

在 Apache 上跑 PHP(现代方案:mpm_event + PHP-FPM)

启用正确的模块

Ubuntu / Debian:

bash
sudo a2dismod mpm_prefork php* || true
sudo a2enmod mpm_event proxy proxy_fcgi setenvif http2 headers rewrite
sudo a2enconf php8.2-fpm
sudo systemctl restart apache2

RHEL:确保在 /etc/httpd/conf.modules.d/ 中加载了 mod_proxymod_proxy_fcgimod_http2 等模块。

创建站点(虚拟主机)

bash
sudo mkdir -p /var/www/example/public
echo "<?php phpinfo();" | sudo tee /var/www/example/public/index.php >/dev/null
sudo chown -R www-data:www-data /var/www/example

Apache 虚拟主机(Ubuntu 风格:/etc/apache2/sites-available/example.conf):

apache
<VirtualHost *:80>
    ServerName example.test
    DocumentRoot /var/www/example/public

    <Directory /var/www/example/public>
        AllowOverride None
        Require all granted
    </Directory>

    # 通过 PHP-FPM 处理 PHP(Unix socket)
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost/"
    </FilesMatch>

    # 安全相关 Header
    Header always set X-Frame-Options SAMEORIGIN
    Header always set X-Content-Type-Options nosniff
    Header always set Referrer-Policy no-referrer-when-downgrade
    Header always set Permissions-Policy "geolocation=()"

    ErrorLog ${APACHE_LOG_DIR}/example-error.log
    CustomLog ${APACHE_LOG_DIR}/example-access.log combined
</VirtualHost>

启用并重载:

bash
sudo a2ensite example.conf
sudo a2dissite 000-default.conf
sudo apachectl configtest
sudo systemctl reload apache2

访问 http://example.test(记得在 /etc/hosts 里加一行:127.0.0.1 example.test)。

如果你必须使用 .htaccess,在 <Directory> 里改成 AllowOverride All。但从性能角度看,推荐把规则移进虚拟主机配置里,保持 AllowOverride None

在 Nginx 上跑 PHP(配合 PHP-FPM)

创建同样的文档根目录:

bash
sudo mkdir -p /var/www/example/public
echo "<?php phpinfo();" | sudo tee /var/www/example/public/index.php >/dev/null
sudo chown -R www-data:www-data /var/www/example

Nginx server block(Ubuntu:/etc/nginx/sites-available/example,再软链到 sites-enabled):

nginx
server {
    listen 80;
    server_name example.test;

    root /var/www/example/public;
    index index.php index.html;

    # 请求路由(适用于 Laravel/WordPress 等)
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP 处理
    location ~ \.php$ {
        # 阻止直接访问不存在的脚本
        try_files $uri =404;

        # Debian/Ubuntu 帮助文件,设置 SCRIPT_FILENAME 等
        include snippets/fastcgi-php.conf;

        # RHEL/其他发行版(备用写法):
        # include fastcgi_params;
        # fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;

        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_read_timeout 60;
    }

    # 拒绝隐藏/敏感文件
    location ~ /\. {
        deny all;
    }

    # 安全相关 Header
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header Referrer-Policy no-referrer-when-downgrade;
    add_header Permissions-Policy "geolocation=()";

    access_log /var/log/nginx/example-access.log;
    error_log  /var/log/nginx/example-error.log warn;
}

启用并测试:

bash
sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Nginx 没有 .htaccess,所有路由和重写都在配置里写死。

反向代理:让 Nginx 站在 Apache 前面

适用于:

  • 老项目大量依赖 .htaccess
  • 想把 TLS / 压缩 / 缓存等逻辑统一放到 Nginx

让 Apache 监听 8080

Ubuntu:编辑 /etc/apache2/ports.confListen 8080,并把虚拟主机改成 <VirtualHost *:8080>

RHEL:在 /etc/httpd/conf/httpd.conf 或相应 vhost 文件里做类似调整。

bash
sudo systemctl restart apache2   # 或 httpd

配置 Nginx(80/443 端口)作为反向代理

nginx
# 在 http{} 里(可选)
# proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=100m inactive=60m use_temp_path=off;

server {
    listen 80;
    server_name example.test;

    location / {
        proxy_pass http://127.0.0.1:8080;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 可选的小缓存
        # proxy_cache my_cache;
        # proxy_cache_valid 200 1m;
        # add_header X-Cache $upstream_cache_status;
    }

    # 静态文件直接由 Nginx 提供
    location ~* \.(jpg|jpeg|png|gif|css|js|ico|svg|woff2?)$ {
        expires 7d;
        access_log off;
        try_files $uri @backend;
    }

    location @backend {
        proxy_pass http://127.0.0.1:8080;
    }
}

TLS 最省心的做法(Let's Encrypt)

前提:

  • 有公网 DNS 解析到你的服务器
  • 80/443 端口可从公网访问
bash
# Apache
sudo certbot --apache -d example.com -d www.example.com

# Nginx
sudo certbot --nginx -d example.com -d www.example.com

选择自动重定向到 HTTPS。Certbot 会自动配置定时任务来续期。

启用 HTTP/2:

  • Apache vhost:Protocols h2 http/1.1
  • Nginx:listen 443 ssl http2;

如果你已经完全切到 HTTPS,可以再加 HSTS(谨慎开启):

nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

HTTP/3(QUIC)需要 Nginx 带 QUIC/HTTP3 模块的构建,并在配置里写类似 listen 443 quic reuseport;——支持情况取决于发行版。

常见 PHP 框架的 URL 重写

Apache(建议写在 vhost 配置里;也可以放在 .htaccess

vhost 中:

apache
<Directory /var/www/example/public>
    AllowOverride None
    Require all granted
</Directory>

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]

.htaccess 版本:

apache
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^ index.php [L]
</IfModule>

Nginx(优雅 URL / Laravel)

nginx
location / {
    try_files $uri $uri/ /index.php?$query_string;
}

WordPress 在 Nginx 上

nginx
location / {
    try_files $uri $uri/ /index.php?$args;
}

拒绝访问敏感文件

Apache:

apache
<FilesMatch "(^\.|\.env|composer\.(json|lock)|Dockerfile|docker-compose\.yml)">
    Require all denied
</FilesMatch>

Nginx:

nginx
location ~* (^/\.|/\.env|composer\.(json|lock)|Dockerfile|docker-compose\.yml)$ {
    deny all;
}

性能:不太容易踩坑的实用默认值

Apache(mpm_event + PHP-FPM)

Ubuntu:/etc/apache2/mods-available/mpm_event.conf(或对应文件):

apache
<IfModule mpm_event_module>
    StartServers              2
    MinSpareThreads          25
    MaxSpareThreads          75
    ThreadLimit              64
    ThreadsPerChild          25
    MaxRequestWorkers       150
    MaxConnectionsPerChild    0
</IfModule>

KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 2

指导原则:

  • 使用 mpm_event + PHP-FPM(非必要不要再用 mod_php + prefork)。
  • MaxRequestWorkers 的大小要结合内存预算来算:Apache + PHP-FPM + OS 的内存占用加起来不能爆。
  • 静态资源尽量交给 Nginx 或 CDN。

Nginx worker 设置

/etc/nginx/nginx.conf

nginx
user www-data;
worker_processes auto;

events {
    worker_connections 1024;  # 高并发可以再调大
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 15;
    types_hash_max_size 4096;

    # Gzip 压缩
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript application/xml application/rss+xml image/svg+xml;

    # 可选:如果有 Brotli 模块
    # brotli on;
    # brotli_comp_level 5;
    # brotli_types text/plain text/css application/json application/javascript application/xml image/svg+xml;
}

动态页面微缓存(谨慎使用)

如果匿名用户的 HTML 可缓存:

nginx
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=micro:10m max_size=256m inactive=5m use_temp_path=off;

server {
  # ...

  location / {
    proxy_pass http://127.0.0.1:9000;  # 或 Apache on :8080

    proxy_cache micro;
    proxy_cache_valid 200 1s;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

    add_header X-Cache $upstream_cache_status;
  }
}

注意:

  • 不要缓存带登录态的请求。
  • 遵守 Cache-Control: private
  • 碰到 Cookie / 认证头时,用 proxy_no_cache 禁掉缓存。

静态资源与缓存头

Nginx

nginx
location ~* \.(?:css|js|jpg|jpeg|png|gif|svg|ico|woff2?)$ {
    expires 30d;
    add_header Cache-Control "public, max-age=2592000, immutable";
    access_log off;
    try_files $uri =404;
}

Apache

apache
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType text/css "access plus 30 days"
  ExpiresByType application/javascript "access plus 30 days"
  ExpiresByType image/svg+xml "access plus 30 days"
  ExpiresDefault "access plus 7 days"
</IfModule>

<IfModule mod_headers.c>
  <FilesMatch "\.(css|js|png|jpg|jpeg|gif|svg|ico|woff2?)$">
    Header set Cache-Control "public, max-age=2592000, immutable"
  </FilesMatch>
</IfModule>

生产环境建议:使用带 hash 的静态资源文件名,例如 app.css?v=<hash>app.<hash>.css,上线时即可安全长缓存。

你真正会去改的 PHP 设置

php.ini(CLI 与 FPM 各有一份):

ini
; 常见调整(下面的大小仅为示例)
memory_limit = 256M
upload_max_filesize = 50M
post_max_size = 50M
max_execution_time = 60
max_input_vars = 2000

; OPcache(对性能帮助巨大)
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0      ; 生产建议设为 0(更新代码后需要 reload)

; 如果你部署很频繁,可以保留自动检测:
; opcache.validate_timestamps=1
; opcache.revalidate_freq=2

大文件上传失败?除了改 PHP 配置,还要在 Nginx 里加:

nginx
client_max_body_size 50M;  # 可写在 server{} 或 http{}

必要的安全加固

使用专门的运行用户

Ubuntu 默认 Apache/Nginx 使用 www-data,RHEL 上是 apachenginx。权限建议:

bash
sudo chown -R www-data:www-data /var/www/example

# 目录 755,文件 644
find /var/www/example -type d -exec chmod 755 {} \;
find /var/www/example -type f -exec chmod 644 {} \;

不要随便给 Web 用户写权限。确实必须写的目录(比如 WordPress 的 wp-content/uploads),最好只给那一个目录开放。

隐藏服务器版本信息

  • Apache:配置 ServerSignature OffServerTokens Prod(例如在 security.conf 中)。
  • Nginx:在 http {} 里加 server_tokens off;

Content Security Policy(CSP)基础示例

nginx
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:;" always;

可以先设得严格一点,再根据实际前端资源慢慢放宽。

防火墙与 SELinux

Ubuntu UFW

bash
sudo ufw allow "Apache Full"   # 或 "Nginx Full"
sudo ufw enable
sudo ufw status

RHEL/Alma/Rocky firewalld

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

SELinux(RHEL 系)

如果 PHP 进程需要访问网络(远程数据库、Redis 等):

bash
sudo setsebool -P httpd_can_network_connect on

如果要从默认路径以外的目录提供文件,需要配相应的 security context,例如用 semanage fcontext + restorecon 调整。

可观测性、日志与健康检查

日志位置:

  • Apache:/var/log/apache2/(Ubuntu)或 /var/log/httpd/(RHEL)
  • Nginx:/var/log/nginx/
  • PHP-FPM:/var/log/php8.2-fpm.log(Ubuntu)或 /var/log/php-fpm/error.log(RHEL)

快速查看:

bash
# 实时查看最后几行
tail -f /var/log/nginx/error.log
tail -f /var/log/apache2/error.log
journalctl -u php8.2-fpm -f   # 或 php-fpm/httpd/nginx

PHP-FPM 状态与 ping(可选,方便监控):

在进程池配置中:

ini
pm.status_path = /status
ping.path = /ping

Nginx 中对应 location:

nginx
location ~ ^/(status|ping)$ {
    allow 127.0.0.1; deny all;   # 暴露时务必限制访问
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}

常见报错

502 Bad Gateway(Nginx → PHP-FPM)

  • 原因:PHP-FPM 挂了或配置错误。
  • 检查:socket 路径/权限、fastcgi_pass 是否一致。
  • 命令:sudo systemctl status php8.2-fpmsudo nginx -t

503 Service Unavailable(Apache + FPM)

  • 原因:MaxRequestWorkers 太小,或 FPM 子进程耗尽。
  • 解决:根据实际内存占用提升 Apache workers 和 pm.max_children

403 Forbidden

  • 原因:文件权限/所有者错误;Apache 少了 Require all granted;RHEL 上可能是 SELinux 拦截。

优雅 URL 访问 404

  • 原因:缺少重写规则。
  • Apache 需要 RewriteRule.htaccess,Nginx 需要合适的 try_files

大文件上传失败

  • 需要同时调:Nginx 的 client_max_body_size 和 PHP 的 upload_max_filesize / post_max_size

端口冲突

  • 不要让 Apache 和 Nginx 同时抢 :80。如果 Nginx 在前面,就把 Apache 改到 :8080,只作为后端。

部署建议

  • 使用发布目录软链:
    /var/www/example/releases/<timestamp> → /var/www/example/current
    Web 根指向 /var/www/example/current/public,发布时只切换 current 链接即可。

  • 如果 opcache.validate_timestamps=0,上线后要重置 OPcache:

    • 可以提供一个受保护的 /opcache-reset HTTP 接口,或直接跑:
      bash
      php -r 'opcache_reset();'
  • 优先使用优雅重载:

    bash
    sudo systemctl reload nginx   # / apache2 / php-fpm

    这样不会粗暴中断现有连接。

快速检查

bash
# 1)安装(选择一个栈)
sudo apt install -y nginx php8.2-fpm  # 或:apache2 libapache2-mod-fcgid php8.2-fpm

# 2)创建站点目录
sudo mkdir -p /var/www/example/public
echo "<?php phpinfo();" | sudo tee /var/www/example/public/index.php >/dev/null
sudo chown -R www-data:www-data /var/www/example

# 3a)Nginx server block
sudo tee /etc/nginx/sites-available/example >/dev/null <<'NGINX'
server {
    listen 80;
    server_name example.test;
    root /var/www/example/public;
    index index.php index.html;

    location / { try_files $uri $uri/ /index.php?$query_string; }

    location ~ \.php$ {
        try_files $uri =404;
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }

    location ~ /\. { deny all; }
}
NGINX

sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# 3b)Apache 虚拟主机(备用方案)
sudo tee /etc/apache2/sites-available/example.conf >/dev/null <<'APACHE'
<VirtualHost *:80>
  ServerName example.test
  DocumentRoot /var/www/example/public

  <Directory /var/www/example/public>
    AllowOverride None
    Require all granted
  </Directory>

  <FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost/"
  </FilesMatch>

  ErrorLog ${APACHE_LOG_DIR}/example-error.log
  CustomLog ${APACHE_LOG_DIR}/example-access.log combined
</VirtualHost>
APACHE

sudo a2ensite example.conf && sudo a2dissite 000-default.conf
sudo apachectl configtest && sudo systemctl reload apache2

# 4)本机 hosts(本地开发)
echo "127.0.0.1 example.test" | sudo tee -a /etc/hosts

# 5)TLS(有公网 DNS 时)
# sudo certbot --nginx  -d example.com -d www.example.com
# sudo certbot --apache -d example.com -d www.example.com

结语

  • 测试结束后,记得删掉临时的 phpinfo() 文件。
  • 不同应用用不同的 PHP-FPM 进程池,有利于安全、资源隔离和调优。
  • 所有「调优」都应该基于指标和监控数据来做——观测永远比拍脑袋有效。
  • 有疑惑时,优先用 reload 而不是 restart 来应用配置,尽量减少对线上连接的影响。
本作品采用《CC 协议》,转载必须注明作者和本文链接