Ubuntu 16.04 Apache虚拟主机配置实战:从零搭建静态与PHP站点

Ubuntu 16.04 Apache虚拟主机配置实战:从零搭建静态与PHP站点

1. 项目概述:为什么在 Ubuntu 16.04 上配 Virtual Host 是每个运维和开发者绕不开的基本功

Apache Virtual Host(虚拟主机)不是什么高深莫测的黑科技,它本质上就是 Apache 服务器的一套“分身术”——让一台物理机器或一个 IP 地址,能同时托管多个完全独立的网站,彼此之间互不干扰、域名隔离、配置自由。你输入example.com看到的是公司官网,输入blog.example.com跳转的是技术博客,输入dev.local又是本地开发环境,背后全靠 Virtual Host 在调度。这个能力在 Ubuntu 16.04 这个 LTS(长期支持)版本上尤其关键:它稳定、社区支持成熟、大量生产环境仍在运行,但它的 Apache 版本是 2.4.x,和旧版 2.2 的语法、模块加载机制、目录权限模型有本质差异。我见过太多人直接把网上搜到的 2.2 配置粘贴进 16.04,结果a2ensite报错、apache2ctl configtest通不过、重启服务直接失败——不是 Apache 不行,是你没摸清它在这套系统里的“脾气”。这篇文章不讲虚的,只讲我在真实项目里反复验证过的路子:从零开始,用最干净的方式,在 Ubuntu 16.04 上搭起两个可立即上线的站点(一个静态页,一个带 PHP 的动态站),每一步都告诉你为什么这么写、删掉哪一行会出什么问题、日志里看到什么错误码就该去查哪个文件。如果你正被AH00526: Syntax error on line X卡住,或者搞不清DocumentRoot<Directory>权限怎么配才不报 403 Forbidden,那这篇就是为你写的。它适合刚装好 Ubuntu 桌面版想搭本地测试环境的前端同学,也适合接手老服务器需要快速排查线上多站点冲突的运维同事——所有操作都在终端里完成,不需要图形界面,不需要第三方面板,只依赖系统原生工具链。

2. 整体设计思路与方案选型:为什么坚持用 a2ensite + 独立配置文件,而不是直接改 httpd.conf

很多人第一次接触 Virtual Host,第一反应是打开/etc/apache2/apache2.conf/etc/apache2/sites-available/000-default.conf,在里面硬塞<VirtualHost *:80>块。这就像在汽车发动机舱里直接用胶带缠电线——能跑,但下次保养准出事。Ubuntu 16.04 的 Apache 包管理逻辑非常清晰:它把配置拆成三层——全局主配置(apache2.conf)、站点可用列表(sites-available/)、站点启用列表(sites-enabled/)。a2ensitea2dissite这两个命令,本质是创建和删除符号链接的封装脚本。我坚持用这套方式,核心原因有三个,全是血泪教训换来的:

第一,可追溯性。你在sites-available/下建myproject.conf,用a2ensite myproject启用,那么sites-enabled/里必然出现一个指向它的软链。哪天要临时下线这个站?a2dissite myproject一执行,软链消失,服务自动 reload,整个过程毫秒级完成,且ls -l /etc/apache2/sites-enabled/一眼就能看清当前启用了哪些站点。而如果直接改000-default.conf,你得手动注释大段代码,稍不留神多删个</VirtualHost>就导致整个 Apache 启动失败,回滚还得靠git diff或备份文件——但很多老服务器压根没配 git。

第二,模块化隔离。每个.conf文件只管自己站点的事:SSL 配置、PHP 处理规则、重写规则、访问控制,全封在里面。比如你要给api.example.com加 CORS 头,就在它的配置里加Header set Access-Control-Allow-Origin "*";给admin.example.com加 IP 白名单,就加<RequireAny><Require ip 192.168.1.0/24></RequireAny>。这些规则绝不会污染其他站点。我曾接手一个客户服务器,000-default.conf里混着七八个<VirtualHost>,其中一段RewriteRule写错了正则,导致所有子域名的图片路径全被重写成 404,排查了三小时才定位到那一行。

第三,与 Ubuntu 包管理深度耦合a2enmoda2ensiteapache2ctl这些命令是 Debian/Ubuntu 为 Apache 定制的“方言”,它们会自动处理依赖关系。比如你启用一个需要rewrite模块的站点,a2ensite不会报错,但apache2ctl configtest会提示Invalid command 'RewriteEngine'——这时你只需a2enmod rewrite,它会自动在mods-enabled/创建软链并 reload。而如果你手动编辑配置,忘了开模块,就得自己去翻mods-available/目录,再手动 ln -s,步骤多一环,出错概率翻倍。

所以我的整体设计就是:每个站点一个独立.conf文件,放在sites-available/;用a2ensite启用,用systemctl reload apache2生效;所有路径、权限、模块调用都严格遵循 Ubuntu 16.04 的默认约定。不追求“最简”,而追求“最稳”——因为线上服务停一分钟,损失的可能不只是流量,还有用户信任。

3. 核心细节解析与实操要点:DocumentRoot、 权限、ServerName 三者如何咬合

Virtual Host 配置里最常出错的,不是复杂的 RewriteRule,而是开头这三行:

DocumentRoot /var/www/myproject <Directory /var/www/myproject> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory>

初学者往往只抄代码,却不理解这三者是如何像齿轮一样咬合运转的。我来拆解每一个环节的真实作用和常见陷阱。

3.1 DocumentRoot:不是“网站根目录”,而是“请求路径的起点映射”

DocumentRoot的字面意思是“文档根目录”,但它真正的角色是URL 路径到文件系统路径的映射基点。当你在浏览器访问http://example.com/css/style.css,Apache 不是直接去/var/www/myproject/css/style.css找文件,而是先看DocumentRoot设在哪,再把 URL 中的/css/style.css“拼接”到这个路径后面。所以DocumentRoot /var/www/myproject+ 请求/index.html= 实际读取/var/www/myproject/index.html

陷阱在于:DocumentRoot 必须是绝对路径,且 Apache 进程必须有读取权限。我见过最典型的错误是把DocumentRoot设成./myproject~/myproject,这在 shell 里看似合理,但 Apache 是以www-data用户身份运行的,它不认识你的~,也不认识相对路径。另一个坑是路径末尾加不加/——DocumentRoot /var/www/myproject/DocumentRoot /var/www/myproject在功能上完全等价,Apache 会自动标准化,但为了统一风格,我建议不加末尾斜杠,避免和<Directory>路径不一致。

3.2 指令块:Apache 的“门禁系统”,不是文件权限的简单复制

<Directory>块的作用,是告诉 Apache:“当用户请求的文件落在这个目录树下时,按以下规则放行”。它和 Linux 文件系统的chmodchown是两套独立的权限体系,必须同时满足才能成功访问。

  • Options Indexes FollowSymLinksIndexes允许目录列表(即没有index.html时显示文件列表),生产环境必须关闭,否则泄露目录结构;FollowSymLinks允许跟随符号链接,开发时方便,但若链接指向/etc/shadow这类敏感文件,就是严重安全漏洞。所以生产环境我通常只写Options -Indexes +FollowSymLinks(减号表示禁用,加号表示启用)。

  • AllowOverride All:这是.htaccess文件的开关。设为All,Apache 就会在每个请求路径下逐级向上查找.htaccess,并执行其中的指令(如重写、认证)。但每次请求都要做这个查找,性能损耗明显。Ubuntu 16.04 默认是None,意味着.htaccess完全无效。我的建议是:开发阶段设All,方便快速调试;上线前必须改为None,把所有.htaccess规则挪到主配置的<Directory>块里。这样既安全又高效。

  • Require all granted:这是 Apache 2.4 的新语法,替代了 2.2 的Order allow,deny+Allow from all。它表示“允许所有来源的请求”。这里有个致命陷阱:如果你写成Require local,那只有本机(127.0.0.1)能访问,外网用户全被拒。而Require all granted是最宽松的,但生产环境应收紧,比如Require ip 192.168.1.0/24或配合mod_auth_basic做密码保护。

最关键的是<Directory>路径必须和DocumentRoot完全一致。我试过把DocumentRoot设为/var/www/myproject,却把<Directory>写成<Directory /var/www/myproject/>(多了斜杠),结果 Apache 启动时报AH00526: Syntax error on line X of /etc/apache2/sites-available/myproject.conf: Invalid directory path。因为 Apache 内部路径标准化后,/var/www/myproject//var/www/myproject被视为不同路径,<Directory>块就失效了,导致Require all granted不生效,最终返回 403 Forbidden。

3.3 ServerName 与 ServerAlias:DNS 解析的“代理声明”,不是域名注册

ServerName example.comServerAlias www.example.com这两行,常被误解为“绑定域名”。其实它们的作用是:当 Apache 收到一个 HTTP 请求,且请求头中的Host:字段匹配这些值时,就将此请求路由给这个 VirtualHost 块处理

重点来了:ServerName是主标识,ServerAlias是别名,但两者都只是字符串匹配,和 DNS 解析无关。你可以在本地/etc/hosts里加一行127.0.0.1 dev.local,然后配ServerName dev.local,就能在浏览器访问http://dev.local;同样,你也可以配ServerName 192.168.1.100,直接用 IP 访问。但ServerName必须是合法的域名格式(不能含下划线,不能以点结尾),且必须和你实际访问时浏览器地址栏输入的 Host 名完全一致(忽略大小写)

常见错误是ServerName写成http://example.com,多了http://,Apache 会直接拒绝启动。另一个坑是大小写敏感性:虽然域名本身不区分大小写,但 Apache 的匹配是字符串比对,ServerName Example.com和请求host: example.com是能匹配的,但为了保险,我一律用小写。

最后强调一个原则:每个 VirtualHost 块必须有且仅有一个ServerName,可以有零个或多个ServerAlias,但所有ServerNameServerAlias的值在整个 Apache 配置中不能重复。否则,当请求Host: example.com到来时,Apache 不知道该交给哪个块处理,就会退回到第一个定义的 VirtualHost(通常是000-default.conf),导致你的站点无法正常响应。

4. 实操过程与核心环节实现:从零搭建两个真实可用的站点(静态+PHP)

现在我们动手,用最标准的流程,在 Ubuntu 16.04 上搭起两个站点:dev.local(纯静态 HTML)和php.dev.local(带 PHP 处理)。全程使用终端命令,不依赖任何 GUI 工具。

4.1 环境准备与基础服务安装

首先确认系统状态。Ubuntu 16.04 默认不预装 Apache,需手动安装。打开终端,执行:

sudo apt update sudo apt install apache2

安装完成后,检查服务状态:

sudo systemctl status apache2

你应该看到active (running)。如果没启动,用sudo systemctl start apache2。此时访问http://localhost或服务器 IP,应该能看到 Apache 的默认欢迎页("It works!")。这说明基础 Web 服务已就绪。

接下来安装 PHP 及其 Apache 模块。Ubuntu 16.04 默认源提供的是 PHP 7.0,我们用它:

sudo apt install php libapache2-mod-php

注意libapache2-mod-php这个包名,它是 PHP 与 Apache 对接的桥梁。安装后,Apache 会自动加载php7_module,你可以在/etc/apache2/mods-enabled/下看到php7.0.loadphp7.0.conf两个软链。验证 PHP 是否生效:

echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php

然后访问http://localhost/info.php,如果看到 PHP 信息页,说明 PHP 模块已正确集成。切记:此时不要删除info.php,它是我们后续调试的利器,但上线前必须删掉,否则泄露服务器信息

4.2 创建第一个站点:dev.local(纯静态)

第一步,创建网站文件目录:

sudo mkdir -p /var/www/dev.local/public_html

-p参数确保父目录不存在时自动创建。接着,创建一个简单的index.html

sudo tee /var/www/dev.local/public_html/index.html << 'EOF' <!DOCTYPE html> <html> <head> <title>Dev Local Site</title> </head> <body> <h1>Welcome to dev.local!</h1> <p>This is a static site served by Apache Virtual Host.</p> </body> </html> EOF

第二步,设置文件所有权和权限。Apache 以www-data用户运行,所以目录必须让www-data可读:

sudo chown -R $USER:www-data /var/www/dev.local sudo chmod -R 755 /var/www/dev.local

chown把所有者设为当前用户(方便你后续编辑),组设为www-datachmod 755表示所有者可读写执行,组和其他人可读执行——这对静态文件足够安全。

第三步,创建 VirtualHost 配置文件:

sudo tee /etc/apache2/sites-available/dev.local.conf << 'EOF' <VirtualHost *:80> ServerAdmin webmaster@localhost ServerName dev.local ServerAlias www.dev.local DocumentRoot /var/www/dev.local/public_html ErrorLog ${APACHE_LOG_DIR}/dev.local_error.log CustomLog ${APACHE_LOG_DIR}/dev.local_access.log combined <Directory /var/www/dev.local/public_html> Options -Indexes +FollowSymLinks AllowOverride None Require all granted </Directory> </VirtualHost> EOF

注意几个关键点:ErrorLogCustomLog指向${APACHE_LOG_DIR},这是 Apache 预定义的变量,实际路径是/var/log/apache2/,这样写更规范;AllowOverride None关闭.htaccess,符合生产环境最佳实践。

第四步,启用站点并测试配置:

sudo a2ensite dev.local.conf sudo apache2ctl configtest

configtest应该输出Syntax OK。如果报错,仔细看错误信息,它会精确指出哪一行、哪个文件出问题。确认无误后,重载 Apache:

sudo systemctl reload apache2

第五步,配置本地 DNS 解析。编辑/etc/hosts

echo "127.0.0.1 dev.local www.dev.local" | sudo tee -a /etc/hosts

现在,在浏览器访问http://dev.local,你应该看到刚才写的欢迎页。恭喜,第一个 Virtual Host 已成功运行!

4.3 创建第二个站点:php.dev.local(支持 PHP)

这个站点要能执行 PHP 代码,所以我们需要额外配置 PHP 处理规则。

第一步,创建目录和测试文件:

sudo mkdir -p /var/www/php.dev.local/public_html sudo tee /var/www/php.dev.local/public_html/index.php << 'EOF' <?php echo "<h1>Welcome to php.dev.local!</h1>"; echo "<p>Current time: " . date('Y-m-d H:i:s') . "</p>"; phpinfo(); ?> EOF

第二步,设置权限(同上):

sudo chown -R $USER:www-data /var/www/php.dev.local sudo chmod -R 755 /var/www/php.dev.local

第三步,创建配置文件。关键区别在于:我们需要告诉 Apache,.php文件要交给 PHP 模块处理。Ubuntu 16.04 的 PHP 模块默认已配置好处理器,但为了明确,我们在<Directory>块里加一行AddType application/x-httpd-php .php,并确保DirectoryIndex包含index.php

sudo tee /etc/apache2/sites-available/php.dev.local.conf << 'EOF' <VirtualHost *:80> ServerAdmin webmaster@localhost ServerName php.dev.local ServerAlias www.php.dev.local DocumentRoot /var/www/php.dev.local/public_html ErrorLog ${APACHE_LOG_DIR}/php.dev.local_error.log CustomLog ${APACHE_LOG_DIR}/php.dev.local_access.log combined <Directory /var/www/php.dev.local/public_html> Options -Indexes +FollowSymLinks AllowOverride None Require all granted AddType application/x-httpd-php .php DirectoryIndex index.php index.html </Directory> </VirtualHost> EOF

AddType指令在这里是冗余的(因为libapache2-mod-php已全局注册),但显式写出能提高可读性,也方便未来切换 PHP 版本时快速定位。

第四步,启用并测试:

sudo a2ensite php.dev.local.conf sudo apache2ctl configtest sudo systemctl reload apache2 echo "127.0.0.1 php.dev.local www.php.dev.local" | sudo tee -a /etc/hosts

访问http://php.dev.local,你应该看到 PHP 信息页,证明 PHP 解析成功。此时,index.php中的date()函数会实时输出时间,phpinfo()会显示所有 PHP 配置,这是我们验证环境是否健康的黄金标准。

4.4 验证与日志分析:如何读懂 Apache 的“求救信号”

配置完成后,别急着庆祝。真正的功夫在日志里。Apache 有两个核心日志文件:

  • 访问日志(Access Log):记录每一次 HTTP 请求,格式为IP - - [时间] "请求方法 URL 协议" 状态码 字节数 "Referer" "User-Agent"。例如:

    127.0.0.1 - - [10/Jan/2024:14:22:33 +0000] "GET /index.php HTTP/1.1" 200 12345 "-" "Mozilla/5.0..."

    状态码200表示成功,404表示文件未找到,500表示服务器内部错误。

  • 错误日志(Error Log):记录配置错误、权限问题、PHP 崩溃等。这才是排错的主战场。例如:

    [Fri Jan 10 14:25:01.123456 2024] [core:error] [pid 1234] (13)Permission denied: [client 127.0.0.1:12345] AH00035: access to /index.php denied (filesystem path '/var/www/php.dev.local/public_html/index.php') because search permissions are missing on a component of the path

这个错误直指核心:www-data用户没有权限进入/var/www/php.dev.local目录(缺少x执行权限,即“搜索”权限)。解决方法就是sudo chmod 755 /var/www/php.dev.local

另一个经典错误是AH00526: Syntax error on line 12 of /etc/apache2/sites-available/php.dev.local.conf: Invalid command 'AddType',这通常是因为你忘了启用mime模块。执行sudo a2enmod mime即可修复。

我养成的习惯是:每次修改配置后,先sudo apache2ctl configtest,再sudo systemctl reload apache2,如果 reload 失败,立刻sudo tail -f /var/log/apache2/error.log查看最新错误。日志是 Apache 唯一诚实的伙伴,它从不说谎,只等你去读。

5. 常见问题与排查技巧实录:那些让我熬夜到凌晨三点的坑

在 Ubuntu 16.04 上配 Virtual Host,90% 的问题都集中在权限、路径、模块和 DNS 四个维度。我把这些年踩过的坑,按发生频率排序,整理成速查表,并附上独家排查技巧。

5.1 403 Forbidden:权限地狱的终极形态

现象:浏览器显示403 Forbidden,日志里出现AH01630: client denied by server configurationAH00035: access to ... denied because search permissions are missing

根本原因:Linux 文件系统权限 + Apache<Directory>权限双重校验失败。

排查四步法

  1. 检查文件系统权限ls -ld /var/www/dev.localls -l /var/www/dev.local/public_html。确保目录有x权限(允许www-data进入),文件有r权限(允许读取)。正确权限应为drwxr-xr-x(目录)和-rw-r--r--(文件)。
  2. 检查 Apache 用户ps aux | grep apache2,确认主进程用户是root,工作进程用户是www-data。如果不是,配置可能被篡改。
  3. 检查<Directory>:确认<Directory>路径和DocumentRoot完全一致,且包含Require all granted
  4. 检查 SELinux/AppArmor:Ubuntu 16.04 默认用 AppArmor。执行sudo aa-status,如果看到apache2enforce模式,可能是它阻止了访问。临时禁用测试:sudo systemctl stop apparmor。如果问题消失,说明是 AppArmor 策略问题,需调整/etc/apparmor.d/usr.sbin.apache2

独家技巧:用sudo -u www-data ls -l /var/www/dev.local/public_html模拟 Apache 用户执行ls,如果报Permission denied,说明文件系统权限不对;如果能列出文件,但浏览器还是 403,则一定是<Directory>配置问题。

5.2 404 Not Found:路径迷宫里的幽灵

现象:浏览器显示404 Not Found,日志里是File does not exist: /var/www/html/xxx

根本原因DocumentRoot设置错误,或请求的 URL 路径在DocumentRoot下确实不存在。

排查三步法

  1. 确认DocumentRoot:在配置文件中找到DocumentRoot行,用ls命令验证路径是否存在且非空。例如sudo ls -la /var/www/dev.local/public_html
  2. 确认请求路径:在浏览器地址栏输入完整 URL,比如http://dev.local/css/style.css,然后检查/var/www/dev.local/public_html/css/style.css是否存在。注意大小写!Linux 文件系统是大小写敏感的。
  3. 检查DirectoryIndex:如果访问http://dev.local/返回 404,但http://dev.local/index.html能打开,说明DirectoryIndex没配对。在<Directory>块里加DirectoryIndex index.html index.php

独家技巧:用curl -I http://dev.local/查看响应头。如果返回HTTP/1.1 404 Not Found,说明是 Apache 找不到文件;如果返回HTTP/1.1 301 Moved Permanently,说明是重定向规则在作怪,要去查mod_rewrite配置。

5.3 500 Internal Server Error:PHP 的无声崩溃

现象:浏览器显示500 Internal Server Error,日志里是AH01215: PHP Parse error: syntax error...AH01071: Got error 'PHP message: PHP Fatal error...'

根本原因:PHP 代码语法错误,或 PHP 扩展缺失(如mysqlipdo_mysql)。

排查两步法

  1. 查看 PHP 错误日志:Ubuntu 16.04 的 PHP 错误默认记录在/var/log/apache2/error.log,但更详细的信息在/var/log/php7.0-fpm.log(如果用了 FPM)或/var/log/apache2/php.dev.local_error.log(如果配置了单独日志)。用sudo tail -f /var/log/apache2/php.dev.local_error.log实时监控。
  2. 启用 PHP 错误显示:在站点配置的<Directory>块里,临时加两行:
    php_flag display_errors on php_flag log_errors on
    然后sudo systemctl reload apache2。这样 PHP 错误会直接显示在浏览器,方便调试。上线前务必删掉这两行!

独家技巧:用php -l /var/www/php.dev.local/public_html/index.php命令行检查 PHP 语法。-l参数是 lint(语法检查),它会报告具体的行号和错误类型,比看浏览器 500 更精准。

5.4 站点不生效:VirtualHost 的“隐身术”

现象:访问http://dev.local,却看到000-default.conf的内容,或另一个站点的内容。

根本原因ServerName/ServerAlias不匹配,或配置文件未启用。

排查三步法

  1. 确认配置已启用ls -l /etc/apache2/sites-enabled/,检查是否有dev.local.conf的软链。如果没有,执行sudo a2ensite dev.local.conf
  2. 确认ServerName匹配:在浏览器地址栏输入的域名,必须和配置文件中的ServerNameServerAlias完全一致(忽略大小写)。用curl -H "Host: dev.local" http://127.0.0.1模拟 Host 头,看是否返回正确内容。
  3. 确认无冲突:执行sudo apache2ctl -S,它会列出所有已加载的 VirtualHost,包括监听端口、ServerName 和配置文件路径。检查dev.local是否在列表中,且*:80端口下没有其他ServerName与之重复。

独家技巧apache2ctl -S的输出中,default server行表示当 Host 头不匹配任何 VirtualHost 时,Apache 会 fallback 到这个默认站点。确保你的000-default.conf是最简配置,只用于兜底,不要在里面塞业务逻辑。

5.5 HTTPS 配置的“甜蜜陷阱”

虽然标题是 HTTP,但很多人紧接着就想上 HTTPS。Ubuntu 16.04 的 Apache 2.4 对 SSL 支持完善,但有个致命陷阱:SSLEngine on必须和Listen 443配合,且SSLCertificateFileSSLCertificateKeyFile路径必须绝对正确,权限必须为600(仅所有者可读写)

常见错误是证书文件权限为644,Apache 启动时会静默失败,systemctl status apache2显示failed,但error.log里只有一句AH02217: ssl_stapling_init_cert: Can't retrieve issuer certificate!。解决方法:sudo chmod 600 /path/to/cert.pem /path/to/key.pem

终极排查口诀

  • 403 → 查权限(文件系统 +<Directory>
  • 404 → 查路径(DocumentRoot+ 文件存在性)
  • 500 → 查代码(PHP 语法 + 扩展)
  • 不生效 → 查启用(a2ensite+apache2ctl -S
  • HTTPS 失败 → 查证书(路径 + 权限 +Listen 443

这些不是教科书理论,是我对着服务器屏幕熬过的夜、删过的配置、重启过的服务换来的经验。记住,Apache 是个老实人,它从不撒谎,只等你去读懂它的日志。

6. 进阶思考与安全加固:从能用到好用的必经之路

配好 Virtual Host 只是起点,真正让服务稳定、安全、可维护,还需要几步关键加固。这些不是“锦上添花”,而是生产环境的底线。

6.1 日志轮转:不让日志吃光磁盘

Ubuntu 16.04 自带logrotate,但默认配置可能不覆盖你的自定义站点日志。编辑/etc/logrotate.d/apache2,在末尾添加:

/var/log/apache2/*_error.log /var/log/apache2/*_access.log { daily missingok rotate 14 compress delaycompress notifempty create 640 root adm sharedscripts postrotate if /etc/init.d/apache2 status > /dev/null ; then /etc/init.d/apache2 reload > /dev/null fi endscript }

这段配置的意思是:每天轮转一次*_error.log*_access.log,保留 14 天,压缩旧日志,创建新日志时权限为640root:adm可读)。postrotate脚本在轮转后自动 reload Apache,确保新日志生效。不加这一步,几个月后/var/log可能被撑爆。

6.2 防止目录遍历:用<LocationMatch>封死危险路径

即使DocumentRoot设得很严,攻击者仍可能通过构造特殊 URL 尝试遍历目录。在主配置或站点配置中加入:

<LocationMatch "^/\.\.|\/\.ht"> Require all denied </LocationMatch>

<LocationMatch>是基于 URL 路径的正则匹配,^/\.\.匹配以/.开头的路径(如/.ssh/),\/\.ht匹配/.htaccess/.htpasswd等。Require all denied直接拒绝所有请求。这是成本最低、效果最好的防护之一。

6.3 性能微调:Worker 模型与 KeepAlive

Ubuntu 16.04 默认用mpm_prefork模块(进程模型),适合 PHP,但内存占用高。如果服务器内存充足(>2GB),可切换到mpm_event(事件模型),并发能力更强:

sudo a2dismod mpm_prefork sudo a2enmod mpm_event sudo systemctl restart apache2

同时,在/etc/apache2/mods-available/mpm_event.conf中调整参数:

StartServers 2 MinSpareThreads 25 MaxSpareThreads 75 ThreadsPerChild 25 MaxRequestWorkers 150 MaxConnectionsPerChild 0

MaxRequestWorkers 150表示最多 150 个并发连接,根据服务器 CPU 和内存调整。KeepAlive OnKeepAliveTimeout 5保持连接复用,减少 TCP 握手开销。

6.4 最后的检查清单:上线前必须做的五件事

  1. sudo apache2ctl configtest:确保语法无误。
  2. sudo systemctl reload apache2:平滑重载,不中断服务。
  3. sudo tail -f /var/log/apache2/error.log:观察重载后是否有新错误。
  4. curl -I http://dev.local:检查 HTTP 状态码是否为200
  5. sudo netstat -tlnp | grep :80:确认 Apache 正在监听80端口,且 PID 正确。

做完这五步,你就可以放心地把域名 DNS 解析指向这台服务器了。Virtual Host 不是终点,而是你掌控 Web 服务的第一把钥匙。它教会你的不仅是配置语法,更是对请求-响应生命周期、权限模型、日志驱动排错的深刻理解。这些能力,会迁移到 Nginx、Caddy,甚至云服务的负载均衡配置中。我至今记得第一次成功用a2ensite启用站点时,那种亲手“点亮”一个网站的踏实感——它不炫酷,但无比真实。