This guide provides comprehensive security hardening recommendations for Lighttpd web server with actionable configuration examples.
Where possible, bind Lighttpd to internal interfaces only:
server.bind = "127.0.0.1"
# Or for specific internal IP
server.bind = "192.168.1.10"
Enable TLS with modern protocols and strong ciphers:
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/etc/ssl/private/example.com.pem"
ssl.privkey = "/etc/ssl/private/example.com.key"
ssl.ca-file = "/etc/ssl/certs/ca-certificates.crt"
# TLS 1.2 minimum (TLS 1.3 preferred)
ssl.openssl.ssl-conf-cmd = (
"MinProtocol" => "TLSv1.2",
"CipherString" => "EECDH+AESGCM:EDH+AESGCM:CHACHA20:!PSK:!DHE:!aNULL:!eNULL"
)
# Diffie-Hellman parameters (generate with: openssl dhparam -out dhparam.pem 4096)
ssl.dh-file = "/etc/ssl/certs/dhparam.pem"
# Elliptic curve for ECDHE
ssl.ec-curve = "secp384r1"
# Disable SSL compression (CRIME attack protection)
ssl.use-compression = "disable"
}
Generate DH parameters:
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
Combine certificate and key:
sudo cat example.com.crt example.com.key > /etc/ssl/private/example.com.pem
sudo chmod 600 /etc/ssl/private/example.com.pem
Prevent information disclosure by customizing or removing the Server header:
# Generic server tag (hides version)
server.tag = "Server"
# Or remove entirely
server.tag = ""
Prevent directory enumeration attacks:
dir-listing.activate = "disable"
# Alternative (older versions)
server.dir-listing = "disable"
Only load required modules to minimize attack surface:
server.modules = (
"mod_openssl", # TLS/SSL support
"mod_access", # Access control
"mod_setenv", # Security headers
"mod_redirect", # HTTP redirects
"mod_rewrite", # URL rewriting
"mod_deflate", # Compression
"mod_expire" # Cache control
)
Modules to avoid unless needed:
mod_webdav - WebDAV support (complex, larger attack surface)mod_ssi - Server-side includes (potential injection risk)mod_cgi - CGI scripts (legacy, security concerns)mod_status - Server status (expose sensitive info if not restricted)Remove or disable default virtual hosts and sample files:
# Remove default site configuration
sudo rm /etc/lighttpd/conf-enabled/00-default.conf
# Remove test files
sudo rm /var/www/html/index.lighttpd.html
Deny access to backup files, configuration files, and version control:
# Block common sensitive file extensions
url.access-deny = ("~", ".inc", ".env", ".config", ".sql", ".log", ".bak", ".backup")
# Block .git directory
$HTTP["url"] =~ "^/\.git" {
url.access-deny = ("")
}
# Block hidden files (dotfiles)
$HTTP["url"] =~ "/\." {
url.access-deny = ("")
}
# Block specific directories
$HTTP["url"] =~ "^/libraries" {
url.access-deny = ("")
}
Limit access to management interfaces by IP:
# Restrict server-status to localhost only
$HTTP["remoteip"] !~ "127.0.0.1|192\.168\.1\." {
$HTTP["url"] =~ "^/server-(status|config)" {
url.access-deny = ("")
}
}
# Configure status module (if needed)
status.status-url = "/server-status"
status.config-url = "/server-config"
Limit allowed HTTP methods:
# Allow only safe methods for static content
$HTTP["url"] =~ "^/static/" {
$HTTP["method"] !~ "^(GET|HEAD)$" {
url.access-deny = ("")
}
}
# Block TRACE method (security risk)
$HTTP["method"] == "TRACE" {
url.access-deny = ("")
}
Prevent unauthorized sites from linking to your resources:
$HTTP["referer"] !~ "^https?://(www\.)?yourdomain\.com/" {
$HTTP["referer"] != "" {
$HTTP["url"] =~ "\.(jpg|jpeg|png|gif|mp4)$" {
url.access-deny = ("")
}
}
}
Never run Lighttpd as root. Use a dedicated unprivileged user:
server.username = "lighttpd"
server.groupname = "lighttpd"
Create the user if it doesn’t exist:
sudo useradd -r -s /usr/sbin/nologin lighttpd
Ensure proper file permissions:
# Set ownership
sudo chown -R lighttpd:lighttpd /var/www/html
# Set permissions (read-only for web server)
sudo chmod -R 755 /var/www/html
# Sensitive directories should not be writable
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
Prevent resource exhaustion and DoS attacks:
# Maximum request size (in KB)
server.max-request-size = 65536
# Maximum connections
server.max-connections = 1024
# Maximum file descriptors
server.max-fds = 2048
# Keep-alive timeouts
server.max-keep-alive-idle = 5
server.max-keep-alive-requests = 100
server.max-read-idle = 60
server.max-write-idle = 60
# Request header size limit
server.max-request-field-size = 8192
Prevent symlink-based attacks:
server.follow-symlink = "disable"
Prevent case-variation attacks:
server.force-lowercase-filenames = "enable"
Add comprehensive security headers to all responses:
server.modules += ("mod_setenv")
setenv.set-response-header = (
# Prevent clickjacking
"X-Frame-Options" => "DENY",
# Prevent MIME-type sniffing
"X-Content-Type-Options" => "nosniff",
# XSS protection (legacy, but still useful)
"X-XSS-Protection" => "0",
# Content Security Policy
"Content-Security-Policy" => "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'",
# HTTP Strict Transport Security (HSTS)
"Strict-Transport-Security" => "max-age=31536000; includeSubDomains; preload",
# Referrer Policy
"Referrer-Policy" => "strict-origin-when-cross-origin",
# Permissions Policy (formerly Feature-Policy)
"Permissions-Policy" => "geolocation=(), microphone=(), camera=(), payment=(), usb=()",
# Cache control for sensitive pages
"Cache-Control" => "no-store, no-cache, must-revalidate, max-age=0",
"Pragma" => "no-cache"
)
Header Values Explained:
| Header | Recommended Value | Purpose |
|---|---|---|
X-Frame-Options |
DENY |
Prevents clickjacking attacks |
X-Content-Type-Options |
nosniff |
Prevents MIME-type sniffing |
X-XSS-Protection |
0 |
Disables legacy XSS filter (use CSP instead) |
Content-Security-Policy |
See above | Controls resource loading |
Strict-Transport-Security |
max-age=31536000 |
Enforces HTTPS for 1 year |
Referrer-Policy |
strict-origin-when-cross-origin |
Controls referrer information |
Permissions-Policy |
See above | Disables browser features |
Enable comprehensive logging for security monitoring:
server.modules += ("mod_accesslog")
# Error log
server.errorlog = "/var/log/lighttpd/error.log"
# Access log with custom format
accesslog.filename = "/var/log/lighttpd/access.log"
accesslog.format = "%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
# Exclude static assets from access log (reduce noise)
$HTTP["url"] =~ "\.(jpg|jpeg|png|gif|css|js|ico|svg|woff|woff2)$" {
accesslog.filename = "/dev/null"
}
Log rotation configuration (/etc/logrotate.d/lighttpd):
/var/log/lighttpd/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 lighttpd lighttpd
sharedscripts
postrotate
systemctl reload lighttpd > /dev/null 2>&1 || true
endscript
}
Force all traffic to use HTTPS:
$HTTP["scheme"] == "http" {
$HTTP["host"] =~ ".*" {
url.redirect = (".*" => "https://%0$0")
}
}
Always test configuration before applying:
# Test configuration syntax
sudo lighttpd -tt -f /etc/lighttpd/lighttpd.conf
# Or with lighttpd-angel (systemd systems)
sudo lighttpd-angel -t -f /etc/lighttpd/lighttpd.conf
Expected output:
Syntax OK
Restart Lighttpd to apply hardening configuration:
# Systemd systems
sudo systemctl restart lighttpd
# Check status
sudo systemctl status lighttpd
# View logs for errors
sudo journalctl -u lighttpd -f
After applying hardening, verify:
server.tag).git, .env, .config)Verify your hardening with these tools:
| Tool | Purpose |
|---|---|
| SSL Labs | TLS/SSL configuration test |
| SecurityHeaders.com | Security headers analysis |
curl -I https://your-domain.com |
Manual header inspection |
nmap --script http-enum -sV your-domain.com |
Service enumeration test |
openssl s_client -connect your-domain.com:443 |
TLS connection test |
Test security headers:
curl -I https://your-domain.com
Expected output should include:
HTTP/2 200
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-frame-options: DENY
x-content-type-options: nosniff
content-security-policy: default-src 'self'
...
Test TLS configuration:
openssl s_client -connect your-domain.com:443 -tls1_2
openssl s_client -connect your-domain.com:443 -tls1_3
Verify TLS 1.0/1.1 are disabled:
openssl s_client -connect your-domain.com:443 -tls1_1
# Should fail with "no protocols available"
Prefer automation? See Lighttpd Ansible Setup for an example playbook.
Prefer containers? See Lighttpd Docker Setup.
Any questions?
Feel free to contact us. Find all contact information on our contact page.