Last updated: 2026-06-25
Lighttpd TLS/SSL Configuration Guide
This guide provides recommended TLS/SSL settings for Lighttpd, a lightweight and fast web server optimized for high-performance environments. Lighttpd 1.4.56+ uses mod_openssl for TLS support.
Prerequisites
- Lighttpd 1.4.56 or later (for
ssl.stapling-fileand session ticket controls) - Lighttpd 1.4.60 or later (for
ssl.verifyclient.ca-fileand related renamed directives) - Lighttpd 1.4.68 or later (for strong PFS cipher defaults; explicit cipher config not required on newer versions)
- OpenSSL 1.1.1 or later (for TLS 1.3 support; OpenSSL 1.1.1 reached EOL in September 2023; OpenSSL 3.x is current)
- A valid SSL/TLS certificate
Lighttpd expects the certificate and private key either in separate PEM files (preferred, since 1.4.53) or combined in a single PEM file.
Enable mod_openssl
Load the TLS module in your Lighttpd configuration (/etc/lighttpd/lighttpd.conf):
server.modules += ("mod_openssl")
Loading the module is not sufficient on its own; TLS must also be enabled per-socket with ssl.engine = "enable" inside the $SERVER["socket"] == ":443" block. See the Complete Configuration Example below.
Protocol Versions
Set the minimum TLS version to 1.2 using ssl.openssl.ssl-conf-cmd:
ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2")
MinProtocol TLSv1.2 has been the default since Lighttpd 1.4.56; TLS 1.0 and 1.1 are disabled on all currently supported releases without explicit configuration. Explicit configuration is still recommended for clarity.
Note: On Lighttpd versions before 1.4.56, ssl.stapling-file and session ticket controls are unavailable. Versions before 1.4.48 do not support ssl.openssl.ssl-conf-cmd at all and rely on legacy directives (ssl.use-sslv2, ssl.use-sslv3, ssl.cipher-list). The first two were removed entirely in 1.4.68; ssl.cipher-list is still accepted but deprecated. Upgrade to a current release - versions this old are no longer maintained.
Cipher Suites
Configure strong cipher suites for TLS 1.2 and TLS 1.3:
ssl.openssl.ssl-conf-cmd += ("CipherString" => "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305")
ssl.openssl.ssl-conf-cmd += ("Ciphersuites" => "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256")
- CipherString - controls TLS 1.2 cipher suites (OpenSSL format). Since Lighttpd 1.4.68, the default is
EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384, which already enforces PFS and excludes weak ciphers; the explicit list above is more restrictive and preferred. - Ciphersuites - controls TLS 1.3 cipher suites. OpenSSL enables all three suites above by default; this directive is only needed to restrict or reorder them.
Since all ciphers in the list are equally strong, server cipher preference should be disabled. This is done via the Options key in ssl.openssl.ssl-conf-cmd; see Session Tickets below. Since Lighttpd 1.4.68, -ServerPreference is the default.
Certificate Configuration
Using a combined PEM file (full certificate chain + private key):
ssl.pemfile = "/etc/lighttpd/ssl/server.pem"
The combined file must contain the server certificate, any intermediate certificates, and the private key. No separate CA file is needed; the chain is read from ssl.pemfile.
Using separate files (Lighttpd 1.4.53+, preferred):
ssl.privkey = "/etc/lighttpd/ssl/privkey.pem"
ssl.pemfile = "/etc/lighttpd/ssl/fullchain.pem"
OCSP Stapling
Lighttpd supports OCSP stapling via ssl.stapling-file (available since 1.4.56). The CA chain for stapling verification is read from ssl.pemfile, so fullchain.pem must include both the server certificate and intermediate certificates:
ssl.stapling-file = "/etc/lighttpd/ssl/ocsp-response.der"
Lighttpd monitors the stapling file automatically: a few minutes before the current OCSP response expires, it polls for an updated file roughly once a minute. If the response contains no Next Update field, the file is re-read once an hour. The file must be readable by the user configured in server.username.
The response file must be generated and kept current by an external process. To generate it:
OCSP_URI=$(openssl x509 -in /etc/lighttpd/ssl/fullchain.pem -noout -ocsp_uri)
openssl ocsp -issuer /etc/lighttpd/ssl/chain.pem \
-cert /etc/lighttpd/ssl/fullchain.pem \
-respout /etc/lighttpd/ssl/ocsp-response.der \
-noverify -no_nonce -url "$OCSP_URI"
chain.pem is the intermediate CA certificate only (the issuer of your server certificate), without the server certificate itself; this is distinct from fullchain.pem, which includes both. -noverify skips signature verification of the response (appropriate for producing the DER file); -no_nonce avoids compatibility issues with some OCSP responders. The lighttpd source tree includes doc/scripts/cert-staple.sh as a reference implementation for scheduling this refresh.
HSTS
Add the HSTS header using mod_setenv:
server.modules += ("mod_setenv")
setenv.add-response-header = ("Strict-Transport-Security" => "max-age=63072000; includeSubDomains; preload")
HTTPS Redirect
Redirect HTTP to HTTPS using mod_redirect:
server.modules += ("mod_redirect")
$SERVER["socket"] == ":80" {
$HTTP["host"] =~ "(.*)" {
url.redirect = ("^/(.*)" => "https://%1/$1")
}
}
Session Tickets
Since Lighttpd 1.4.56, session ticket encryption keys are automatically rotated every 8 hours by default, with three overlapping keys giving a 24-hour ticket lifetime. This built-in rotation limits the forward secrecy exposure of session tickets without any additional configuration, and is safe for single-server deployments.
Server cipher order preference (discussed in Cipher Suites) is disabled via the same Options directive:
ssl.openssl.ssl-conf-cmd += ("Options" => "-ServerPreference")
To also disable session tickets entirely for maximum forward secrecy, add -SessionTicket:
ssl.openssl.ssl-conf-cmd += ("Options" => "-ServerPreference,-SessionTicket")
For multi-worker or multi-server deployments that need to share session state across processes or hosts, use ssl.stek-file to point to a shared key file that you rotate externally. When ssl.stek-file is set, the built-in automatic rotation is disabled and key management becomes your responsibility.
Mutual TLS (mTLS)
Standard TLS authenticates only the server. Mutual TLS adds client authentication, requiring the connecting client to also present a certificate. This is an optional hardening step, not required for standard web deployments. It is most useful for internal services, admin interfaces, and APIs where you control all connecting clients.
Add the following inside your HTTPS listener block. Since Lighttpd 1.4.60, the client CA directive is named ssl.verifyclient.ca-file (previously ssl.ca-file, which is deprecated):
ssl.verifyclient.ca-file = "/etc/lighttpd/ssl/client-ca.crt"
ssl.verifyclient.activate = "enable"
ssl.verifyclient.enforce = "enable"
ssl.verifyclient.depth = 2
- ssl.verifyclient.ca-file - Path to the CA certificate file used to verify client certificates.
- ssl.verifyclient.activate - Enable client certificate verification. Must be
"enable"for any client cert checking to occur. - ssl.verifyclient.enforce - Set to
"enable"to reject connections without a valid client certificate. Set to"disable"to allow connections without a cert while still verifying any cert that is presented. - ssl.verifyclient.depth - Maximum certificate chain depth. Set to 2 if client certs are issued by an intermediate CA.
To export the client certificate DN to CGI or FastCGI applications:
ssl.verifyclient.exportcert = "enable"
ssl.verifyclient.username = "SSL_CLIENT_S_DN_CN"
See RFC 8446 ยง4.3.2 for the TLS Certificate Request specification, and Wikipedia: Mutual authentication for a general overview.
Complete Configuration Example
server.modules += (
"mod_openssl",
"mod_redirect",
"mod_setenv"
)
# HTTPS listener
# Note: ":443" binds IPv4 only; add an equivalent $SERVER["socket"] == "[::]:443" { ... } block with the same TLS settings for IPv6
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
# Certificate files
ssl.privkey = "/etc/lighttpd/ssl/privkey.pem"
ssl.pemfile = "/etc/lighttpd/ssl/fullchain.pem"
# Protocol and cipher settings
# Add -SessionTicket to Options to disable session tickets entirely for maximum forward secrecy
ssl.openssl.ssl-conf-cmd = (
"MinProtocol" => "TLSv1.2",
"CipherString" => "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305",
"Ciphersuites" => "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256",
"Options" => "-ServerPreference"
)
# OCSP stapling (optional; lighttpd re-reads this file automatically before expiry)
# ssl.stapling-file = "/etc/lighttpd/ssl/ocsp-response.der"
# HSTS
setenv.add-response-header = ("Strict-Transport-Security" => "max-age=63072000; includeSubDomains; preload")
}
# HTTP to HTTPS redirect
$SERVER["socket"] == ":80" {
$HTTP["host"] =~ "(.*)" {
url.redirect = ("^/(.*)" => "https://%1/$1")
}
}
Security Notes
The cipher suite and protocol configuration in this guide addresses the following known TLS vulnerabilities:
- POODLE (CVE-2014-3566, 2014): SSL 3.0 is disabled. TLS_FALLBACK_SCSV was added in OpenSSL 1.0.1j / 1.0.2 (October 2014); SSL 3.0 disabled by default in OpenSSL 1.1.0 (August 2016).
- BEAST (CVE-2011-3389, 2011): Mitigated by recommending TLS 1.2 as the minimum; AEAD-only ciphers eliminate the CBC padding oracle.
- CRIME (CVE-2012-4929, 2012): TLS compression is off by default in OpenSSL 1.1.0+; do not enable it.
- Lucky13 (2013): AEAD-only cipher list eliminates CBC padding timing side-channels entirely.
- FREAK (CVE-2015-0204, 2015): EXPORT-grade ciphers are excluded from the cipher string. Removed from OpenSSL 1.1.0 (August 2016).
- LOGJAM (CVE-2015-4000, 2015): Short-key DHE is excluded; only ECDHE key exchange is recommended.
- Sweet32 (CVE-2016-2183, 2016): 3DES is excluded from the cipher string.
- ROBOT (2017): Static RSA key exchange is excluded; only ECDHE is recommended.
- Downgrade attacks: TLS_FALLBACK_SCSV prevents protocol version rollback.
- Renegotiation injection (CVE-2009-3555, 2009): Secure renegotiation is enforced by default in OpenSSL 0.9.8m+; TLS 1.3 removes renegotiation entirely.
The following are not addressable through TLS configuration alone:
- Heartbleed (CVE-2014-0160, 2014): A memory disclosure bug in OpenSSL 1.0.1 through 1.0.1f. Fixed in OpenSSL 1.0.1g (April 7, 2014). Addressed by patching OpenSSL, not by TLS configuration.
- BREACH (CVE-2013-3587, 2013): Exploits HTTP-level response compression (gzip/deflate on responses). Mitigated at the application layer by disabling HTTP compression or using BREACH countermeasures; TLS configuration cannot prevent it.
- DROWN (CVE-2016-0800, 2016): Requires SSLv2 to be enabled on any server sharing the same private key. Ensure SSLv2 is disabled on all services that use the same certificate and key pair.
Verification
Test the configuration and restart Lighttpd:
lighttpd -t -f /etc/lighttpd/lighttpd.conf
systemctl restart lighttpd
Test your TLS connection:
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
Test your configuration externally with the Mr.DNS SSL/TLS Certificate Check.