Long before I ever touched a JavaScript framework, I was maintaining a static HTML site where every page repeated the same header and footer by hand. The day I discovered Server-Side Includes, I stopped copy-pasting navigation markup across dozens of files and let Apache do the assembling for me. It’s an old technology, but I still reach for it on small static sites where a full build pipeline would be overkill.
In this post I’ll cover what SSI is, why I still use it, and exactly how I configure it in Apache.
What Is Server-Side Includes (SSI)?
SSI is a simple interpreted language embedded directly inside HTML comments that Apache processes before it sends the page to the browser. A directive looks like this:
<!--#include virtual="/includes/header.html" -->
When Apache serves the file, it swaps that directive for the actual content of header.html — the browser never sees the directive at all, only the final assembled HTML.
Why I Still Use SSI Today
- Static site reuse: sharing a common header/footer across dozens of static pages without a build step.
- Dynamic snippets: showing the current date, a visitor’s IP, or a file’s last-modified time.
- Lightweight conditionals: showing/hiding content blocks without spinning up a full application server.
- Legacy maintenance: some older sites I’ve inherited rely on it, so understanding it is part of the job.
Prerequisites
- Apache HTTP Server installed and running
- Root or sudo access to edit config
- Basic HTML familiarity
Step 1: Enable the Include Module
SSI processing runs through mod_include.
Debian/Ubuntu:
sudo a2enmod include
sudo systemctl restart apache2
RHEL/CentOS: I confirm the module is loaded in httpd.conf:
LoadModule include_module modules/mod_include.so
Step 2: Enable SSI Processing for a Directory
I edit the virtual host or directory block to allow SSI execution:
<Directory /var/www/html>
Options +Includes
AddType text/html .shtml
AddOutputFilter INCLUDES .shtml
</Directory>
Options +Includesenables SSI processing in this directory.AddType text/html .shtmltells Apache to treat.shtmlfiles as HTML.AddOutputFilter INCLUDES .shtmlroutes.shtmlfiles through the SSI parser before they go out.
I reload Apache after changes:
sudo apachectl configtest
sudo systemctl reload apache2
Optional: Processing .html Files as SSI
If I want existing .html files to support SSI without renaming everything to .shtml (there’s a performance trade-off I get into below):
AddOutputFilter INCLUDES .html
Step 3: My First SSI File
/var/www/html/includes/header.html:
<header>
<h1>My Static Site</h1>
<nav><a href="/">Home</a> | <a href="/about.shtml">About</a></nav>
</header>
/var/www/html/index.shtml:
<!DOCTYPE html>
<html>
<head><title>Home</title></head>
<body>
<!--#include virtual="/includes/header.html" -->
<p>Welcome to my site. Today's date is <!--#echo var="DATE_LOCAL" --></p>
<!--#include virtual="/includes/footer.html" -->
</body>
</html>
Load the page in a browser and the header and footer show up as if they’d always been part of the file.
Common SSI Directives I Use
Include a file
<!--#include virtual="/includes/footer.html" -->
<!--#include file="footer.html" -->
virtual is relative to the document root; file is relative to the current file’s directory. I stick with virtual for portability.
Echo a variable
<!--#echo var="DATE_LOCAL" -->
<!--#echo var="LAST_MODIFIED" -->
<!--#echo var="REMOTE_ADDR" -->
Set a variable
<!--#set var="name" value="Ahmad" -->
<p>Hello, <!--#echo var="name" -->!</p>
Execute a command (I use this sparingly and carefully)
<!--#exec cmd="uptime" -->
Conditional logic
<!--#if expr="$QUERY_STRING = /admin/" -->
<p>Admin view</p>
<!--#else -->
<p>Public view</p>
<!--#endif -->
Show file modification time
<!--#flastmod virtual="index.shtml" -->
Show file size
<!--#fsize virtual="/downloads/manual.pdf" -->
Real-World Use Cases
- A documentation site with dozens of static pages sharing one navigation bar.
- An internal status dashboard showing live command output, locked down carefully.
- A small-business site maintained by non-developers editing raw HTML, relying on SSI for consistent headers/footers.
- Legacy intranet applications that predate modern templating.
Security Best Practices I Follow
SSI’s exec directive is powerful, and I treat it with real caution. If a directory allows both file uploads and SSI exec, someone who uploads a .shtml file could run arbitrary shell commands on the server.
- I disable
execunless I genuinely need it, usingIncludesNOEXECinstead ofIncludes:Options +IncludesNOEXECThis still permits#include,#echo,#set, and#if, but blocks#exec. - I never enable SSI processing in directories where users can upload files.
- I avoid exposing sensitive environment variables via
#echo var="...". - I keep SSI-enabled directories entirely separate from upload directories as an extra layer of protection.
Performance Optimization
- I prefer
.shtmlover blanket.htmlprocessing. ApplyingAddOutputFilter INCLUDESto every.htmlfile forces Apache to parse each one for SSI directives, even files with none — a distinct extension limits that parsing to files that actually need it. - I add caching headers (
mod_expires) for static assets referenced inside SSI pages. - I avoid
#execon high-traffic pages, since spawning a subprocess per request gets expensive fast. - For high-traffic sites, I’ve migrated from SSI to a static site generator that pre-renders includes at build time instead of on every request.
Mistakes I’ve Made
- Forgetting
AddOutputFilter INCLUDESand wondering why<!--#include -->shows up literally on the page. - Using
file=with an absolute path when onlyvirtual=reliably supports document-root-relative paths. - Leaving
#execenabled on a publicly writable directory. - Skipping
apachectl configtestbefore reloading, which broke the server once on a syntax error I didn’t catch. - Expecting SSI to support full programming logic — it’s intentionally minimal and I don’t lean on it for anything complex.
Troubleshooting
SSI directives appear as literal text in the browser I check that mod_include is enabled and AddOutputFilter INCLUDES targets the right file extension.
500 Internal Server Error on SSI pages I check the error log:
sudo tail -f /var/log/apache2/error.log
Usually a referenced include file doesn’t exist, or exec is disabled but being called.
Includes not updating after editing the source file I clear any browser or intermediate proxy cache; SSI itself re-processes on every request by default.
FAQs
Is SSI still relevant in 2026? For small static sites and legacy systems, yes, in my experience. For anything beyond simple content reuse, I reach for a static site generator or a real backend framework instead.
Can SSI replace a templating engine like PHP or a JS framework? No. It’s intentionally limited to basic includes, variable substitution, and simple conditionals — no loops, no complex data structures, no database connectivity.
Does SSI hurt page load performance for visitors? Minimal for small includes, but every SSI-enabled request needs server-side processing rather than being served as pure static content, which is slightly slower at scale.
Summary and Key Takeaways
- SSI lets Apache assemble HTML pages from reusable fragments at request time, using simple embedded directives.
- I enable it with
mod_include,Options +Includes, andAddOutputFilter INCLUDESscoped to a specific extension like.shtml. - I use
IncludesNOEXECinstead ofIncludesunless I genuinely need command execution —#execis a real security risk. - SSI still earns its place on small static sites, but I don’t treat it as a substitute for modern templating at scale.
References
- Apache mod_include Documentation: https://httpd.apache.org/docs/current/mod/mod_include.html
- Apache SSI Tutorial: https://httpd.apache.org/docs/current/howto/ssi.html
- Apache Security Tips: https://httpd.apache.org/docs/current/misc/security_tips.html
