SSI配置的核心答案是:在服务器端开启SSI模块并把目标文件扩展名改为.shtml,就能在不改动页面框架的前提下,用一行指令把公共头部、底部和片段动态嵌入到任意网页中。这套机制至今仍是中小站点做公共代码复用的轻量方案,尤其在Apache和Nginx环境下,配置成本极低,下文直接给出两个主流服务器的完整步骤,以及排查故障的实操路径。
SSI配置前需要搞清楚的三件事
很多人在第一步就走偏,以为SSI是某种编程语言,实际上SSI是一组服务器端指令,只负责“把文件内容插入到另一个文件”这件事。
- 执行原理:当用户请求.shtml文件时,服务器先扫描文件内容,识别并替换
<!--#include -->这类指令,再把处理完的HTML发送给浏览器,客户端看到的是纯静态页面,这点和PHP、JSP完全不同。 - 适用场景:网站有统一的导航栏、页脚、公告栏,且页面数量多、不适合套用前端框架的存量站点,也适合用来做简单的服务端状态注入,比如显示当前时间、文件大小、环境变量。
- 致命局限:SSI没有逻辑判断和循环能力,也不做数据缓存,动态数据渲染请交给后端程序,SSI只解决“包含”这一个需求。
Apache环境下的SSI配置方法(含完整步骤)
Apache是支持SSI最成熟的服务器,配置路径清晰,按下面三步操作即可。
第一步:开启mod_include模块
在终端执行以下命令(以Ubuntu/Debian为例):
sudo a2enmod include sudo systemctl restart apache2
在CentOS/RHEL系中,直接编辑/etc/httpd/conf/httpd.conf,找到#Include相关行,确保没有被注释,然后重启httpd服务。
第二步:指定.shtml文件的处理方式
这里需要区分两个指令:
AddType text/html .shtml:让服务器把.shtml当作HTML文件来设置MIME类型。AddOutputFilter INCLUDES .shtml:让服务器对.shtml文件执行SSI解析。
在实际配置文件中,这两行通常联合使用,缺一不可,只加AddType而漏掉AddOutputFilter,是Apache下SSI不生效的最常见原因。
第三步:目录权限的Options配置
在<Directory>块中,把原本的Options Indexes FollowSymLinks改为:
Options Indexes FollowSymLinks Includes
注意Includes这个关键词就是允许SSI执行的开关,修改后重启Apache,配置即生效。
实操验证:写一个最简单的include
在网站根目录建两个文件:

footer.html
<div>这里是脚注,原始文件时间:2026-01-01</div>
test.shtml
<html> <body> <!--#include virtual="footer.html" --> </body> </html>
访问http://你的域名/test.shtml,如果看到footer.html的内容被渲染出来,就说明SSI配置成功。virtual关键词用于包含站内路径,如果涉及CGI变量注入,也可以使用exec指令,但在共享主机上通常会被禁用。
Nginx环境下的SSI配置方法
Nginx对SSI的支持同样成熟,配置相对集中,主要在server或location块内完成。
基础配置代码
在nginx.conf中,目标location或server块内加入:
location / {
ssi on;
ssi_types text/html;
ssi_last_modified on;
}
ssi on:开启SSI功能。ssi_types:声明哪些MIME类型参与解析,如果页面是HTML,写text/html即可。ssi_last_modified:开启后会让SSI文件的Last-Modified响应头取原始文件的修改时间,有利于协商缓存,建议开启。
修改完成后执行nginx -t检查语法,然后nginx -s reload平滑重载。
注意:include指令的路径语法有差异
Apache的virtual在Nginx中同样有效,但更多教程推荐使用file,
<!--# include file="footer.html" -->
Nginx下需要格外注意:file路径相对于当前文档根目录,而virtual路径相对于网站访问根路径,填错路径不会报错,但页面上那个区域就是空白。
nginx ssi配置不生效的常见原因排查
Nginx的SSI配置出错几率比Apache高,原因是大多数人在改完配置后忽略了两个隐蔽细节:
- 根因1:响应头被压缩或缓存插件拦截,如果开启了Gzip或使用FastCGI Cache,解析过程可能在缓存层就被截断,SSI指令原样输出,排查方法是临时关闭
gzip on和proxy_cache,再刷新页面看是否生效。 - 根因2:HTTP子请求被禁用,SSI本质上会发起一个内部子请求去获取被包含的文件,如果配置了
proxy_pass且后端返回的状态码不是200,子请求就会失败。
逐项排除时,使用下面命令查看Nginx错误日志是最快的路径:
tail -f /var/log/nginx/error.log
如果日志中出现[error] ... upstream prematurely closed connection

这类记录,就能顺藤摸瓜,定位到后端FastCGI或代理服务的问题。
网站shtml文件打开是空白?多半是这些细节没做到
配置好SSI之后,很多人访问.shtml页面,结果页面顶部一片空白、整页内容未加载出来,这种情况多数不是配置项写错,而是文件本身的编码和权限出问题。
编码不一致是首要元凶
被include的文件如果保存为UTF-8带BOM格式,而主文件是纯UTF-8无BOM,浏览器解析时会在页面顶部输出一个不可见字符,看上去就像“页面空白”,用记事本或VS Code打开文件,把编码统一调整为UTF-8 without BOM即可。
文件权限或SELinux拦截
Apache或Nginx进程用户(通常是www-data或nginx)必须对被include的文件拥有读取权限,把文件放在网站根目录下,执行:
chmod 644 footer.html
如果服务器开启了SELinux(CentOS常见),还需要为目录添加httpd_sys_content_t上下文标签,这个坑在云服务器上非常普遍。
使用stat命令确认文件修改时间
在服务器上执行stat footer.html,查看文件的修改时间,如果你的站点开启了页面缓存(例如Nginx FastCGI Cache或Apache mod_cache),旧缓存可能保留了更早的时间戳,这会导致SSI更新后的内容不刷新,此时清空缓存目录即可恢复。
SSI与ESI的选型对比:看场景才不踩坑
说到服务端包含,很多人会拿ESI(Edge Side Includes)来和SSI比较,行业共识认为这两者的核心能力范围并不重叠,不能直接互替。
| 对比项 | SSI | ESI |
|---|---|---|
| 设计目标 | 解决公共片段复用 | 解决大型页面按区块缓存 |
| 逻辑能力 | 弱,仅条件变量基本判断 | 强,支持多种条件、HTTP请求头判断 |
| 适用容器 | Apache、Nginx均可 | Varnish、Akamai等CDN边缘节点 |
| 典型业务 | 公司官网、老系统运维 | 电商首页、新闻门户高并发场景 |
| 配置成本 | 几步操作即可完成 | 需要理解缓存分层架构 |
如果你只是在静态页面里嵌入一段公共代码,选择SSI就够了,如果你在做高并发动态站的全页面缓存拆分,那应该去研究ESI,而不是盯着SSI不放。
静态页面用SSI后的维护效率提升
做了多年企业站运维,我最有体感的是改版时的工作量变化。
以前一个50页的官网需要改页脚,要么写脚本全量替换,要么开编辑器逐页手工操作,耗时至少2个小时,用SSI之后,我只需改一次footer.html,50个页面刷新后全部同步生效,关键要点是:

- 头部、底部、侧边栏拆成三个独立文件,解决90%的公共代码维护需求。
- 公告类的临时内容(比如春节放假通知)单独做一个
notice.html,直接include进来,过期后改这个文件即可,不会动到主页面框架。 - 配合
<!--#config timefmt="%Y-%m-%d" -->指令,还能在页面底部统一输出“更新日期”,这比写在HTML注释里更规范。
近年来很多建站公司用这套思路在降低托管成本,比起着手写一套模板引擎,在Web服务器层直接做处理显然更轻量,如果你正在纠结动态页改造,不妨先把公共片段抽出来用SSI跑通,让页面先瘦下来。
SSI配置常见问题与操作验证
很多人在配置成功后会忽略一个问题:如何验证是服务器在执行SSI,还是浏览器开挂了?
测试方法很简单:访问一个包含SSI指令的.shtml页面,查看源代码(Ctrl+U),如果源码里能看到原始的<!--#include -->注释,说明服务器没有解析;如果源码里是完整的HTML内容,说明SSI执行成功,这个区分方式是排查高级故障时最常用的一招。
另一个容易被忽视的地方是.shtml扩展名的后缀大小写,Windows服务器(IIS)通常不区分大小写,但Linux下的Apache和Nginx是严格区分.shtml和.SHTML的,为了避免低级失误,全站统一使用小写后缀。
Q&A:SSI配置中常见的两个实际问题
问题1:配置SSI后页面加载明显变慢,是不是SSI本身性能很差?
不一定,SSI本身是轻量级处理,性能损耗极小,多数情况是因为被include的文件过大,或者主页面发起大量的子请求导致服务器并发压力上升,更常见的原因是开启了日志中每一条子请求都记录,撑大了访问日志,拖慢磁盘写入速度,而非SSI本身的问题。
排查时统计一下单个页面的子请求数量,控制在5个以内,性能不会有明显劣化,业内专家指出,SSI适合“少而小”的包含策略,而不是把整块业务代码塞进include里。
问题2:SSI能不能做用户登录状态的个性化显示?
SSI可以做很基础的变量判断(比如根据环境变量显示不同内容),但它无法读取Cookie或Session状态,也不具备真正的逻辑运算能力,要做“已登录显示用户名,未登录显示登录按钮”这类个性化内容,最好依靠后端脚本,或者用前端JS读取Cookie后局部渲染,配置一个模板引擎(如Jinja2或Blade)来解决这类需求更划算。
做SSI配置前先想清楚:你要解决的到底是“代码复用”,还是“动态交互”,想清楚了,工具的选择就不会跑偏。
