ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Apache配置实战:虚拟主机与URL重写核心指南

Apache配置实战:虚拟主机与URL重写核心指南 1. 从一脸懵到心中有数Apache配置体系与核心指令看到“Apache配置”这几个字很多刚接触服务器运维的朋友第一反应是头疼。我的感觉恰恰相反——Apache的配置其实是一套非常经典且逻辑清晰的体系只要你摸清了它的骨架后面不管是搭虚拟主机、写URL重写规则还是排查报错都是顺着同样的思路走。先说清楚Apache是什么。它是一个开源Web服务器软件职责就是接收浏览器的请求比如你输入www.example.com然后把对应的网页文件返回给用户。全世界大量网站跑在Apache上它的配置方式直接决定了网站如何被访问、如何被保护、如何做跳转和伪静态。这篇文章围绕“虚拟主机”和“URL重写”两个核心场景展开这两个场景覆盖了日常工作中80%以上的Apache配置需求。1.1 配置文件“总-分结构”与不同发行版的差异Apache的配置文件最核心的那一份叫httpd.confLinux上通常是/etc/httpd/conf/httpd.conf或/etc/apache2/apache2.confWindows上则位于Apache安装目录的conf/子目录。很多新手习惯把所有配置一股脑堆进httpd.conf结果文件越来越长后面排查问题像大海捞针。我的建议是默认的httpd.conf保持精简把虚拟主机独立成文件再用Include指令引进来。主流Linux发行版的差一点要分清CentOS/RHEL/Fedora系主配置在/etc/httpd/conf/httpd.conf额外配置建议放在/etc/httpd/conf.d/下以.conf结尾就会自动加载。Debian/Ubuntu系主配置在/etc/apache2/apache2.conf站点配置放/etc/apache2/sites-available/启用站点用a2ensite命令禁用用a2dissite。Windows/macOS通常在安装目录的conf/下手动管理Include conf/extra/httpd-vhosts.conf。无论哪家底层的配置语法完全一致区别只在于默认路径和辅助脚本。我最顺手的方式是遵守发行版惯例这样以后交接到别人手里对方一眼就能找到配置位置不会骂人。1.2 必须刻进脑子里的核心指令动手写虚拟主机之前先掌握几个高频指令它们都会在配置里反复出现Listen指定Apache监听的IP和端口例如Listen 80。服务器上如果跑了多个站点不同端口或不同域名就要合理规划。ServerRootApache安装目录的根路径不要轻易改动改动了相对路径会连锁报错。DocumentRoot一个站点对应的磁盘目录浏览器请求的URL会映射到这个目录下找文件。DirectoryIndex访问目录时默认加载的文件比如DirectoryIndex index.html index.php。ServerName、ServerAlias定义站点的房主身份和别名后面配置虚拟主机时是绝对主角。ErrorLog、CustomLog分别指定错误日志和访问日志的写入路径排查问题的第一现场。配置修改后务必养成验证语法习惯。运行apachectl configtest有的机器上命令是httpd -t如果输出Syntax OK再执行重载# CentOS系 apachectl configtest systemctl reload httpd # Ubuntu系 apachectl configtest systemctl reload apache2重载reload与重启restart的区别重载不中断已有连接只重新读取配置重启则会中断所有连接。生产环境建议优先用reload避免在业务高峰期制造人为断连。1.3 修改配置前的“自我体检”我刚入行时经常直接编辑配置文件然后重启遇到Job for httpd.service failed的报错才慌慌张张找语法问题。后来养成了一个好习惯每次改动前先备份一份比如cp httpd.conf httpd.conf.bak_20240115改完立刻configtest。这套“备份-修改-测试-重载”流程看起来慢实际救场速度最快。2. 虚拟主机配置一台服务器扛起多个网站虚拟主机VirtualHost解决的问题很现实一台服务器只有一份Apache但你想在上面同时跑多个网站。比如你有一台阿里云或腾讯云的机器想放两个项目——一个博客、一个企业官网——它们的域名不同目录不同互不干扰。在Apache里创建多个VirtualHost块就能做到一个进程服务多个站点。2.1 三种虚拟主机流派IP、端口、域名怎么选虚拟主机按绑定方式分三类基于IP每个站点独占一个IP比如机器配了多网卡或多个IP地址VirtualHost 192.168.1.10:80对应站点AVirtualHost 192.168.1.11:80对应站点B。优点是干净彻底缺点是IP资源宝贵现实中很少用。基于端口同一IP不同端口VirtualHost *:80和VirtualHost *:8080分别对应不同站点。多用于内部测试、管理后台这类不想让用户默认端口访问的场景。基于域名最实用同一IP、同一80端口靠ServerName区分。举个例子服务器IP是203.0.113.5两个域名blog.example.com和corp.example.com都解析到这个IP。浏览器输入blog.example.com时Apache通过请求头里的域名判断应该交给哪个VirtualHost处理。这种方式的成本最低也是生产环境的主流选择。2.2 一个域名一站点的标准配置模板以CentOS环境为例在/etc/httpd/conf.d/下新建vhost-blog.conf内容如下VirtualHost *:80 ServerName blog.example.com ServerAlias www.blog.example.com DocumentRoot /var/www/blog ErrorLog logs/blog-error.log CustomLog logs/blog-access.log common /VirtualHost对应的目录权限配置建议放进同文件或独立文件Directory /var/www/blog Options -Indexes AllowOverride All Require all granted /Directory这里有三个细节值得展开。第一DocumentRoot目录一定要提前创建好并在里面放一个可访问文件比如index.html否则启动不会报错但访问时会出现403或404。我第一次配置时忘记建目录浏览器直接甩了403还傻乎乎以为权限写错了。第二AllowOverride All允许该目录下的.htaccess文件覆盖全局配置。URL重写、密码保护等很多小而美的功能可以放.htaccess里。但生产环境我更推荐把规则直接写进VirtualHost或Directory原因有二一是多一层.htaccess解析会略微降低性能二是分散的配置不利于全局审查和排错。第三Options -Indexes前面的减号表示为“去除此功能”意思是禁止目录列表展示。没有它当目录里缺省index文件时Apache会直接把文件列表亮给你看——相当于把服务器文件清单送给所有访客这是个非常常见的信息泄露隐患。2.3 基于端口和基于IP的实操写法如果要在同一台机器测试两个项目但暂时不想解析域名基于端口最省事。默认监听80再增加一个8080监听Listen 80 Listen 8080VirtualHost *:8080 ServerName localhost DocumentRoot /var/www/test-project /VirtualHost访问http://127.0.0.1:8080就会落到测试项目。注意重启或重载后确认8080端口正常监听netstat -tlnp | grep 8080基于IP的场景少见但写法要会假设网卡绑定了192.168.1.10和192.168.1.11两个地址直接写VirtualHost 192.168.1.10:80、VirtualHost 192.168.1.11:80有时还需要加上NameVirtualHost指令老版本才需要新版Apache已废弃。2.4 虚拟主机配置中的常见坑与应对默认站点抢夺流量如果Apache自带一个default.conf或httpd.conf里定义了非虚拟主机文档目录某些请求会落到它头上。建议把默认站点注释或停掉让所有站点都以VirtualHost形式显式管理。重复监听或重复ServerName两个VirtualHost写同一个域名和端口Apache启动时只警告不报错但访问行为会变得魔幻。Scheme是“后定义的覆盖先定义的”这种歧义尽早消除。ServerName警告启动时提示Could not reliably determine the servers fully qualified domain name解决方法是全局配置里写一个ServerName localhost只是消除告警无碍功能。PHP站点的目录权限如果通过mod_php跑PHPRequire all granted还能接受如果改用FPM方式则Directory里的配置要小心别把不该暴露的目录权限放开。3. URL重写用RewriteEngine给网站换一套“访问地图”虚拟主机解决的是“请求应落到哪个站”的问题URL重写解决的是“URL该怎么处理、往哪里转”的问题。Apache最强大的URL操作工具是mod_rewrite它可以在请求到达文档目录之前执行规则实现将外部友好的URL如/article/1024.html映射为内部真实路径也可以把旧地址301跳转到新页面根据浏览器类型切换不同页面。理解它的核心逻辑是读懂关键词“URL重写”的关键。3.1 加载模块与重写流程确保在配置文件中加载了重写模块。CentOS系统执行httpd -M | grep rewrite输出rewrite_module (static)或rewrite_module (shared)表示已启用。Debian系用a2enmod rewrite启用然后重启服务。如果未启用所有RewriteRule都会静默失败页面不跳转、不伪静态也没有任何报错这是最让人抓狂的情况。接下来是重写流程的直觉理解。客户端请求http://blog.example.com/article/1024.htmlApache先匹配VirtualHost再检查该目录下是否有重写规则。如果规则存在Apache把URL交给mod_rewrite引擎引擎按照你写的规则逐一匹配、替换最终内部定位到/var/www/blog/article.php?id1024。用户看到的URL始终是/article/1024.html但服务器实际执行的是另一个路径。3.2 RewriteRule的四个组成部分最常用的语法是RewriteRule pattern substitution [flags]pattern正则表达式匹配当前URL的路径部分。注意它匹配的不是完整URL而是移除了协议和主机名之后的路径比如/article/1024.html匹配的pattern是article/1024.html。substitution把匹配到的URL替换成的目标地址。可以是站内路径也可以是完整URL。[flags]附加标志用方括号括起、逗号分隔。常用标志我列在下面都是我实测过的高频项标志作用典型场景LLast立即停止本轮重写几乎所有业务规则都建议加RRedirect触发浏览器跳转配合301/302使用旧域名迁移、http转httpsNCNoCase大小写不敏感用户手抖输入大小写混杂QSAQuery String Append把原查询串追加到新URL分页/带参数的伪静态NENoEscape不对输出中的特殊字符转义防止中文路径被编码END终止重写流程新版支持比L更彻底主要用于重写后仍需访问示例把不带www的域名301跳到带www的版本。RewriteEngine On RewriteCond %{HTTP_HOST} ^example\.com$ [NC] RewriteRule ^(.*)$ http://www.example.com/$1 [R301,L]这里有三个容易踩的坑规则顺序就是执行顺序写在前面的优先匹配匹配后带L标志就终止后续规则。所以通用规则往前放特殊规则往后放。多条规则时正则写不好会死循环。比如你写了个重写规则把 a 转成 b又在另一条规则里把 b 转成 a浏览器就会疯狂跳转最终报错ERR_TOO_MANY_REDIRECTS。伪静态场景里如果替换目标是一个真实存在的文件路径比如index.php但替换后的路径又被另一条规则匹配会继续重写。需要理清规则顺序或者在合适的位置加L标志。3.3 RewriteCond给重写规则装上“条件限位”RewriteCond通常是RewriteRule前面的伴侣它决定规则何时生效。格式为RewriteCond test-string cond-pattern [flags]服务器变量要用百分号语法%{...}不要和捕获组用$1混淆。常见变量包括%{HTTP_HOST}请求域名、%{REQUEST_URI}请求路径、%{REMOTE_ADDR}客户端IP、%{HTTP_USER_AGENT}浏览器标识。经典的例子是让HTTP自动跳转HTTPSRewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R301,L]另一类是移动端适配。根据User-Agent判断是手机还是电脑跳转到对应站点RewriteEngine On RewriteCond %{HTTP_USER_AGENT} Mobile|Android|iPhone [NC] RewriteRule ^$ /mobile/index.html [L]注意RewriteCond与RewriteRule之间的逻辑关系如果同一个RewriteRule前有多条RewriteCond默认是“并且”关系全部满足才执行。如果想让“或者”生效需要给条件加[OR]标志例如RewriteCond %{HTTP_USER_AGENT} iPhone [NC,OR] RewriteCond %{HTTP_USER_AGENT} Android [NC] RewriteRule ^$ /mobile/ [L]3.4 高频实战场景伪静态、防盗链、去掉入口文件PHP伪静态。家CMS系统动态地址是article.php?id1024page2想改成/article/1024/2这种萝卜干风格RewriteEngine On RewriteRule ^article/([0-9])/?$ article.php?id$1 [L,QSA] RewriteRule ^article/([0-9])/([0-9])/?$ article.php?id$1page$2 [L,QSA]两个规则要顺序排列短路径优先。QSA的作用是保留原有查询参数比如?fromwechat也会带到后端。防盗链。别人网站直接引用你站点的图片白白消耗你的带宽RewriteEngine On RewriteCond %{HTTP_REFERER} !^$ RewriteCond %{HTTP_REFERER} !^https?://(www\.)?example\.com [NC] RewriteRule \.(jpg|jpeg|png|gif)$ http://www.example.com/hotlink.jpg [R302,L]这个规则的意思是来源非空的请求中如果来源域名不是example.com访问图片资源时重新导向到/hotlink.jpg。这样盗链者看到的是一张警示或替代图而不是你的原图。隐藏入口文件。很多框架如ThinkPHP、CodeIgniter需要URL形如index.php/controller/method去掉中间那块难看的index.phpRewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php [L]这里的!-f和!-d表示“如果请求不是真实存在的文件、不是真实存在的目录才执行重写”。有了这两个条件CSS、JS、图片这些实际存在的文件不会被打进框架避免框架空转导致性能问题。3.5 重写规则调试与性能考虑写重写规则容易翻车调试时最直接的办法是在虚拟主机配置里临时调高日志级别LogLevel alert rewrite:trace6trace6会输出极其详细的重写流程日志包含每条规则是否匹配、是否跳过了替换。排查完记得恢复LogLevel warn否则日志量会迅速膨胀把磁盘填满。我在一个线上事故里就吃过这个亏当时为了调一条跳转规则开了trace6忘了恢复结果半天时间/var/log/httpd/error_log涨了几百兆。生产环境还要注意重写规则是一次次正则匹配规则过多或正则写得过于复杂会影响请求吞吐。几十条规则对高端硬件没什么感觉但几百条废话正则叠加性能会被拖垮。能合并的规则尽量合并能用RewriteMap解决的问题别在配置文件里硬写几十条if逻辑。4. 排查实录启动失败、404、500的根因定位前面提到的很多坑能在配置阶段避免。但真实世界里问题总会在你最没防备的时候出现。我们部门历年来在Apache上遇到的报错集中在几类我把排查路径梳理成一份可以照着做的清单。4.1 启动失败先看语法再看端口最后扒日志服务器重启后Apache起不来最常见的三个原因配置语法错误。执行apachectl configtest如果输出类似Syntax error on line 12 of /etc/httpd/conf/httpd.conf: Invalid command RewirteRule这样的提示说明第12行附近写错了很可能是拼写错误。注意RewriteRule不能写成RewirteRule这种拼写错误我见过不止一次而且越急越发现不了。端口被占。执行netstat -tlnp | grep :80如果发现nginx或httpd已经占用了80新启动的Apache必然失败。要么停掉旧进程要么改监听端口。权限问题。Apache工作进程对DocumentRoot目录没有读执行权限或者日志文件没权限写入。检查目录权限即可通常要保证从根目录到站点目录的每一级路径都至少有r-x权限。有个细节要提醒apachectl configtest只能查出语法错误查不出逻辑错误。比如虚拟主机配了相同域名和端口它只会在日志里记一条警告不会中止启动。所谓“启动失败”除了配置语法更要看error_log在启动瞬间输出的信息那是第一手线索。4.2 URL重写不生效按“模块-作用域-顺序-日志”四层剥洋葱碰到重写规则不生效我的排查顺序固定如下第一层模块到底加载了吗。没有rewrite_module一切都白搭。用httpd -M | grep rewrite验证。第二层规则写在哪儿。如果在.htaccess要确认对应的Directory里开了AllowOverride All如果写在VirtualHost里要确认RewriteEngine On写在这个虚拟主机内部写在全局的RewriteEngine On不会自动传到每个虚拟主机。第三层顺序对不对。Apache 从上到下执行规则如果之前有一条规则用了L标志提前终止后面的规则就永远不会执行。把重点规则挪到前面或者删除前面规则里多余的L。第四层看重写日志。开启trace6观察某个请求实际匹配了哪一条规则有没有走到替换步骤有没有因为[L]中断。这一步通常能精准定位问题。4.3 日志分析从error_log与access_log反推现场Apache访问日志默认格式里每一行对应一次请求包含客户端IP、时间、请求行、状态码、User-Agent。排查“某个用户反馈无法访问”时我先从这个用户访问时间段对应的access_log里捞状态码403目录权限或.htaccess拒绝。404DocumentRoot下没有对应文件或重写规则映射到了不存在的路径。500多半是PHP代码错误或重写规则死循环。301循环检查是否同时存在把 http 转 https 和 https 继续转 http 的规则互相打架。错误日志error_log里会给出更直接的线索尤其PHP fatal错误或Request exceeded the limit of 10 internal redirects请求超过10个内部重定向差不多说明重写写成了死循环。4.4 一套问题速查表现象常见原因解决方向启动失败语法错误配置拼写、缺括号、缺引号apachectl configtest定位行号启动失败端口占用nginx或其他httpd占用80netstat -tlnp查占用改端口或停进程访问403目录权限不足、缺index文件检查Require指令和目录的读执行权限访问500Ruby/PHP错误、重写死循环查error_log尝试禁用重写规则rewrite不生效模块未启用、AllowOverride未开httpd -M检查模块确认Directory指令死循环跳转多条规则间互相重写、HTTPS跳转条件写反逐条梳理规则开启trace6观察访问极慢规则太多了、日志太高精简规则恢复日志级别5. 安全加固与性能微调上线前别省这一步配置写好、功能跑通还远不是终点。Apache是公网服务上线即暴露在各类扫描和攻击之下。我见过太多配置流程完整、安全防护为零的项目这里分享几个成本极低但效果明显的加固手段。5.1 隐藏版本号与禁用服务器签名Apache默认会在HTTP响应头里吐出类似Server: Apache/2.4.41 (Unix)的信息等于告诉攻击者你的精确版本降低了他寻找可利用漏洞的门槛。修改配置ServerTokens Prod ServerSignature OffServerTokens Prod让响应头只显示Apache不显示具体版本ServerSignature Off禁止页面底部出现服务器版本信息。这一改不影响任何业务逻辑但能让恶意扫描少拿一份情报。5.2 保护.htaccess和敏感文件如果启用了.htaccess一定要防止它被外部直接下载里面可能有重写规则和访问控制的内部逻辑FilesMatch ^\.ht Require all denied /FilesMatch同理数据库备份这类敏感文件放在DocumentRoot外面别给用户留一个/site.sql的下载入口。5.3 防止目录浏览与限制危险请求方法Options -Indexes一定要有避免目录无index时就地列文件。还建议限制不必要的HTTP方法Location / LimitExcept GET POST HEAD OPTIONS Require all denied /LimitExcept /Location只保留常用方法比如TRACE方法可被利用做跨站追踪DELETE、PUT这类方法非必要就不开放。5.4 少花钱的性能调优三件套性能调优是另一个大话题这里只说三个即改即生效的项目启用压缩。启用mod_deflate后对常见的HTML、CSS、JS内容进行gzip压缩能把传输体积压缩到原来的四分之一左右体验提升立竿见影。调整KeepAlive。KeepAlive On配合KeepAliveTimeout 5让客户端复用TCP连接减少握手次数。注意超时别调太长否则在并发高时会让Apache进程被无用连接占满。开启缓存头。在静态资源响应中加缓存头比如IfModule mod_expires.c ExpiresActive On ExpiresByType text/css access plus 7 days ExpiresByType application/javascript access plus 7 days ExpiresByType image/jpeg access plus 30 days /IfModule浏览器会缓存这些资源再次访问就不再重复下载服务器压力直线下降。我在实际项目中还发现一个容易被忽略的细节日志格式也会影响性能。默认的common格式每个请求写一行如果并发很高磁盘IO会成为瓶颈。可以把CustomLog改成缓冲式写入或者交给专门的日志收集器比如rsyslog处理而不是让Apache进程直接写文件对极端流量下的稳定性有明显帮助。这台开源Web服务器从1995年走到今天配置体系始终保持着高度的可读性和可预测性。哪怕如今Nginx大行其道Apache对其模块架构的坚持依然影响深远。虚拟主机帮我们在一台物理机上优雅地服务多个域名URL重写则把动态业务URL塑造成更利于分享和SEO的样子。这两块能力掌握扎实你对Apache的掌控力就已经超过绝大多数只会改改端口和目录的运维新手了。最后再唠叨一句生产环境改配置永远备份先行语法检查、重载、观察日志三步走遇事不要慌先看error_log。
返回列表