What is Lighttpd?
Lighttpd (pronounced "lighty") is an open-source, high-performance web server designed with speed, security, and low resource consumption in mind. Originally created by Jan Kneschke in 2003 as a proof-of-concept to handle the C10K problem—serving 10,000 concurrent connections on a single server—Lighttpd has evolved into a mature, production-ready HTTP server used by some of the most traffic-intensive websites in the world.
At its core, Lighttpd employs an event-driven, single-threaded architecture powered by asynchronous I/O multiplexing (using mechanisms like epoll, kqueue, or poll). This design allows it to handle thousands of simultaneous connections without spawning a separate thread or process for each request, dramatically reducing memory footprint and CPU context-switching overhead compared to traditional process-based servers like Apache's prefork model.
Lighttpd supports HTTP/1.1, HTTP/2 (via the mod_h2 module), WebSockets, FastCGI, SCGI, reverse proxying, URL rewriting, virtual hosting, SSL/TLS with SNI, and a rich module ecosystem. Its configuration syntax is straightforward and logical, making it approachable for both beginners and seasoned system administrators.
Why Lighttpd Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →In an era where infrastructure efficiency directly translates to cost savings and environmental impact, Lighttpd occupies a crucial niche. Here are the key reasons developers and operations teams choose it:
Exceptional Performance Under Load
Lighttpd's event-driven model shines when serving static assets, proxying requests, or handling long-lived connections like Server-Sent Events or WebSockets. It consistently outperforms many alternatives in benchmarks involving high concurrency, delivering lower latency and higher throughput while using a fraction of the system resources.
Minimal Memory Footprint
A typical Lighttpd process consumes only a few megabytes of RAM, even under moderate load. This makes it ideal for embedded systems, virtual private servers with limited resources, containerized environments, and edge computing deployments where every megabyte counts.
Predictable Resource Usage
Unlike process-forking servers where memory usage grows linearly with concurrent connections, Lighttpd's memory consumption remains relatively flat. This predictability simplifies capacity planning and prevents out-of-memory scenarios during traffic spikes.
Security by Design
Lighttpd maintains a strong security track record. Its codebase is compact and auditable, and it ships with sensible defaults—directory listing is disabled, request filtering is built-in, and modules can be precisely controlled. The project responds quickly to vulnerability reports and maintains a clear security advisory process.
Ideal for Microservices and CDN Edge Nodes
When deployed as a reverse proxy or static asset server at the edge, Lighttpd excels. Its ability to handle massive concurrent connections with minimal resources makes it perfect for content delivery network edge nodes, API gateways, and microservice architectures where lightweight, fast proxy layers are essential.
Installation
Lighttpd is available in the package repositories of virtually all major Linux distributions. Below are installation instructions for the most common environments.
Debian / Ubuntu
sudo apt update
sudo apt install lighttpd
After installation, the service starts automatically. Verify its status:
sudo systemctl status lighttpd
RHEL / CentOS / Fedora / AlmaLinux
On RHEL-based systems, Lighttpd is typically available via EPEL (Extra Packages for Enterprise Linux). Enable EPEL first, then install:
sudo dnf install epel-release
sudo dnf install lighttpd
sudo systemctl enable lighttpd
sudo systemctl start lighttpd
Alpine Linux
sudo apk add lighttpd
sudo rc-service lighttpd start
Arch Linux
sudo pacman -S lighttpd
sudo systemctl enable lighttpd
sudo systemctl start lighttpd
Building from Source
For environments requiring specific compile-time options or the latest features, building from source is straightforward. First, install build dependencies:
# Debian/Ubuntu
sudo apt install build-essential libpcre3-dev zlib1g-dev libssl-dev
# RHEL/Fedora
sudo dnf install gcc make pcre-devel zlib-devel openssl-devel
Download the source tarball from lighttpd.net and compile:
wget https://download.lighttpd.net/lighttpd/releases-1.4.x/lighttpd-1.4.76.tar.xz
tar xf lighttpd-1.4.76.tar.xz
cd lighttpd-1.4.76
./configure --with-openssl --with-pcre --with-zlib
make
sudo make install
The default installation prefix is /usr/local. The main configuration file will be located at /usr/local/etc/lighttpd/lighttpd.conf.
Core Configuration Structure
Lighttpd's configuration lives in a single primary file—typically /etc/lighttpd/lighttpd.conf—with optional includes from a conf.d or conf-enabled directory. The syntax is clean and procedural: settings are applied in the order they appear, and later directives can override earlier ones.
Here is the essential anatomy of a minimal working configuration:
# /etc/lighttpd/lighttpd.conf
server.modules = (
"mod_indexfile",
"mod_access",
"mod_accesslog",
"mod_staticfile"
)
server.document-root = "/var/www/html"
server.port = 80
server.username = "www-data"
server.groupname = "www-data"
index-file.names = ( "index.html", "index.htm" )
mimetype.assign = (
".html" => "text/html",
".htm" => "text/html",
".txt" => "text/plain",
".css" => "text/css",
".js" => "application/javascript",
".png" => "image/png",
".jpg" => "image/jpeg",
".jpeg" => "image/jpeg",
".gif" => "image/gif",
".svg" => "image/svg+xml",
".ico" => "image/x-icon"
)
server.errorlog = "/var/log/lighttpd/error.log"
accesslog.filename = "/var/log/lighttpd/access.log"
Let's break down each directive:
- server.modules: Explicitly lists the modules to load. Lighttpd only loads what you specify, keeping the runtime lean.
- server.document-root: The filesystem path from which static files are served.
- server.port: The TCP port to bind. Use
80for HTTP,443for HTTPS. - server.username / server.groupname: The unprivileged user and group the worker process switches to after binding ports.
- index-file.names: Files to look for when a directory URL is requested.
- mimetype.assign: Maps file extensions to MIME types. Lighttpd does not ship with a built-in MIME database; you define what you need.
- server.errorlog / accesslog.filename: Paths for error and access logging. Setting these explicitly ensures you know where logs are written.
Working with Modules
Lighttpd's functionality is extended through modules. The base server handles only the HTTP protocol engine; everything else—logging, access control, compression, FastCGI, proxying—comes from modules. Loading a module is as simple as adding its name to server.modules.
Here are the most commonly used modules and their purposes:
- mod_indexfile — Serves index files (index.html) for directory requests.
- mod_dirlisting — Generates directory listings (disable in production).
- mod_staticfile — Serves static files from disk.
- mod_access — Controls access based on IP, hostname, or other criteria.
- mod_accesslog — Writes Apache-compatible access logs.
- mod_compress — Applies gzip or brotli compression to responses.
- mod_redirect — Issues HTTP redirects based on URL patterns.
- mod_rewrite — Rewrites URLs internally using regular expressions.
- mod_fastcgi — Proxies requests to FastCGI backends (PHP-FPM, etc.).
- mod_proxy — Acts as a reverse proxy to HTTP backends.
- mod_openssl — Provides SSL/TLS support (mod_openssl in 1.4.x, mod_gnutls or mod_mbedtls also available).
- mod_h2 — Enables HTTP/2 support (requires TLS).
- mod_wstunnel — Tunnels WebSocket connections to backends.
- mod_setenv — Sets environment variables or response headers.
- mod_expire — Adds Cache-Control and Expires headers for static assets.
To enable compression, for example, you add "mod_compress" to server.modules and configure it:
server.modules += ( "mod_compress" )
compress.cache-dir = "/var/cache/lighttpd/compress"
compress.filetype = ( "text/html", "text/css", "text/javascript", "application/javascript", "application/json", "text/xml", "image/svg+xml" )
compress.allowed-encodings = ( "gzip", "deflate", "br" )
Note the += operator, which appends to the existing module list rather than replacing it. This is useful in included configuration snippets.
Virtual Hosting
Lighttpd supports name-based virtual hosting (using the HTTP Host header) via the mod_simple_vhost module, or you can define virtual hosts explicitly with conditional blocks. The explicit approach offers finer control.
Explicit Virtual Hosts with Conditionals
Use the $HTTP["host"] conditional to match specific hostnames:
server.modules += ( "mod_accesslog" )
# Default (catch-all) host
server.document-root = "/var/www/default"
$HTTP["host"] == "example.com" {
server.document-root = "/var/www/example.com"
accesslog.filename = "/var/log/lighttpd/example.com.access.log"
}
$HTTP["host"] == "api.example.com" {
server.document-root = "/var/www/api"
# Different configuration for the API subdomain
}
$HTTP["host"] =~ "^(.+\.)?blog\.example\.com$" {
server.document-root = "/var/www/blog"
# Regex match catches blog.example.com and sub.blog.example.com
}
Conditionals can be nested, allowing you to match on host plus other criteria:
$HTTP["host"] == "secure.example.com" {
$SERVER["socket"] == ":443" {
ssl.pemfile = "/etc/lighttpd/certs/secure.example.com.pem"
}
}
Simple Virtual Hosting
For scenarios where you want virtual hosts to map directly to directories named after the hostname, mod_simple_vhost automates this:
server.modules += ( "mod_simple_vhost" )
simple-vhost.server-root = "/var/www/vhosts"
simple-vhost.document-root = "/htdocs"
simple-vhost.default-host = "default.example.com"
With this setup, a request for example.com serves files from /var/www/vhosts/example.com/htdocs/. The default-host handles requests that don't match any existing directory.
SSL/TLS Configuration
Securing your server with HTTPS is essential. Lighttpd uses mod_openssl (built on OpenSSL) for TLS termination. The configuration requires a certificate and private key, typically combined in a single PEM file.
Generating Certificates with Let's Encrypt
Use Certbot or another ACME client to obtain certificates:
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
Certbot places the certificate at /etc/letsencrypt/live/example.com/fullchain.pem and the private key at /etc/letsencrypt/live/example.com/privkey.pem. Lighttpd expects both in one file, so concatenate them:
sudo cat /etc/letsencrypt/live/example.com/privkey.pem \
/etc/letsencrypt/live/example.com/fullchain.pem \
> /etc/lighttpd/certs/example.com.pem
sudo chmod 600 /etc/lighttpd/certs/example.com.pem
Enabling TLS
Add mod_openssl to your modules and configure the listening socket:
server.modules += ( "mod_openssl" )
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/etc/lighttpd/certs/example.com.pem"
ssl.use-sslv2 = "disable"
ssl.use-sslv3 = "disable"
ssl.cipher-list = "ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM"
ssl.honor-cipher-order = "enable"
}
# Optional: redirect HTTP to HTTPS
$HTTP["scheme"] == "http" {
$HTTP["host"] =~ "^(.*)$" {
url.redirect = (".*" => "https://%1$0")
}
}
Server Name Indication (SNI)
To serve multiple domains with different certificates on the same IP and port, use SNI matching:
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
$HTTP["host"] == "example.com" {
ssl.pemfile = "/etc/lighttpd/certs/example.com.pem"
}
$HTTP["host"] == "another.org" {
ssl.pemfile = "/etc/lighttpd/certs/another.org.pem"
}
# Fallback certificate for unmatched hosts
ssl.pemfile = "/etc/lighttpd/certs/default.pem"
}
Enabling HTTP/2
Once TLS is configured, HTTP/2 can be enabled with mod_h2:
server.modules += ( "mod_h2" )
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/etc/lighttpd/certs/example.com.pem"
h2.protocol = "enable"
}
PHP Integration via FastCGI
Lighttpd does not embed a PHP interpreter. Instead, it communicates with PHP-FPM (FastCGI Process Manager) via the FastCGI protocol. This separation of concerns improves security and allows independent scaling of the PHP backend.
Installing and Configuring PHP-FPM
# Debian/Ubuntu
sudo apt install php-fpm
# RHEL/Fedora
sudo dnf install php-fpm
Ensure PHP-FPM listens on a Unix socket or TCP socket. The default pool configuration typically uses a Unix socket at /run/php/php-fpm.sock or /var/run/php/php8.3-fpm.sock. Check your distribution's specifics:
sudo grep 'listen' /etc/php/*/fpm/pool.d/www.conf
Wiring Lighttpd to PHP-FPM
Add mod_fastcgi to your modules and configure the handler:
server.modules += ( "mod_fastcgi" )
fastcgi.server = (
".php" => ((
"socket" => "/run/php/php-fpm.sock",
"bin-path" => "/usr/bin/php-cgi",
"max-procs" => 4,
"broken-scriptfilename" => "enable",
"allow-x-sendfile" => "enable"
))
)
For TCP-based PHP-FPM (e.g., when using containers or remote backends):
fastcgi.server = (
".php" => ((
"host" => "127.0.0.1",
"port" => 9000,
"broken-scriptfilename" => "enable"
))
)
Path Info Handling
Some PHP applications (like WordPress in certain configurations) rely on PATH_INFO. Configure it explicitly:
fastcgi.server = (
".php" => ((
"socket" => "/run/php/php-fpm.sock",
"bin-path" => "/usr/bin/php-cgi",
"max-procs" => 4,
"broken-scriptfilename" => "enable",
"fix-pathinfo" => 1
))
)
Complete PHP Hosting Configuration Example
server.modules = (
"mod_indexfile",
"mod_access",
"mod_accesslog",
"mod_staticfile",
"mod_fastcgi",
"mod_setenv"
)
server.document-root = "/var/www/html"
server.port = 80
server.username = "www-data"
server.groupname = "www-data"
index-file.names = ( "index.php", "index.html", "index.htm" )
mimetype.assign = (
".html" => "text/html",
".php" => "application/x-httpd-php",
".css" => "text/css",
".js" => "application/javascript"
)
fastcgi.server = (
".php" => ((
"socket" => "/run/php/php-fpm.sock",
"bin-path" => "/usr/bin/php-cgi",
"max-procs" => 4,
"broken-scriptfilename" => "enable"
))
)
# Security: deny access to hidden files and sensitive paths
$HTTP["url"] =~ "/\." {
url.access-deny = ("")
}
static-file.exclude-extensions = ( ".php", ".inc", ".sql", ".bak" )
Reverse Proxy Configuration
Lighttpd excels as a reverse proxy, forwarding client requests to backend application servers. The mod_proxy module handles HTTP proxying, while mod_fastcgi handles FastCGI backends.
Basic HTTP Reverse Proxy
server.modules += ( "mod_proxy" )
$HTTP["host"] == "app.example.com" {
proxy.server = ( "" => ((
"host" => "127.0.0.1",
"port" => 3000
)))
}
This forwards all requests matching app.example.com to a Node.js or Python application listening on port 3000.
Advanced Proxy with Headers
When proxying, you should forward relevant client headers so the backend knows the original requester:
$HTTP["host"] == "app.example.com" {
proxy.server = ( "" => ((
"host" => "127.0.0.1",
"port" => 3000
)))
# Preserve original request information
setenv.add-request-header = (
"X-Forwarded-For" => "%{REMOTE_ADDR}e",
"X-Forwarded-Proto" => "%{SCHEME}e",
"X-Forwarded-Host" => "%{HTTP_HOST}e"
)
}
Proxy with Load Balancing
Lighttpd can distribute requests across multiple backends using weighted or round-robin balancing:
proxy.balance = "round-robin"
$HTTP["host"] == "api.example.com" {
proxy.server = ( "" => (
( "host" => "10.0.1.10", "port" => 8080 ),
( "host" => "10.0.1.11", "port" => 8080 ),
( "host" => "10.0.1.12", "port" => 8080 )
))
proxy.balance = "fair" # or "round-robin", "least-connections"
}
WebSocket Proxy
For WebSocket connections, use mod_wstunnel alongside mod_proxy:
server.modules += ( "mod_wstunnel", "mod_proxy" )
$HTTP["url"] =~ "^/ws/" {
wstunnel.server = ( "" => ((
"host" => "127.0.0.1",
"port" => 8080
)))
wstunnel.frame-type = "text"
}
URL Rewriting and Redirects
Lighttpd provides two modules for URL manipulation: mod_redirect for external redirects (HTTP 301/302) and mod_rewrite for internal rewrites (the URL changes internally but the client sees the original).
Redirects
server.modules += ( "mod_redirect" )
# Redirect old page to new location
$HTTP["url"] == "/old-page.html" {
url.redirect = (".*" => "/new-page.html")
}
# Canonicalize www to non-www (or vice versa)
$HTTP["host"] == "www.example.com" {
url.redirect = (".*" => "https://example.com$0")
}
# Force HTTPS
$HTTP["scheme"] == "http" {
$HTTP["host"] =~ "example.com" {
url.redirect = (".*" => "https://example.com$0")
}
}
Internal Rewrites
Rewrites are essential for clean URLs in CMS platforms and web applications:
server.modules += ( "mod_rewrite" )
# WordPress-style front controller
url.rewrite-if-not-file = (
"^/(wp-admin|wp-includes)/(.*)" => "$0",
"^/(.*)$" => "/index.php/$1"
)
# Drupal-style clean URLs
url.rewrite-if-not-file = (
"^/(.*)$" => "/index.php?q=$1"
)
The url.rewrite-if-not-file directive only rewrites URLs that don't correspond to existing files on disk, preventing static assets from being incorrectly routed to the front controller.
Access Control and Security
Lighttpd's mod_access provides flexible access control. You can deny or allow requests based on IP address, URL patterns, HTTP methods, and more.
IP-Based Access Control
# Deny by default, allow specific IPs for admin area
$HTTP["url"] =~ "^/admin" {
$HTTP["remoteip"] !~ "^(10\.0\.0\.|192\.168\.1\.|127\.0\.0\.1)" {
url.access-deny = ("")
}
}
# Allow only specific IP ranges
$HTTP["remoteip"] =~ "^(203\.0\.113\.|198\.51\.100\.)" {
# Trusted IPs get full access
} else {
$HTTP["url"] =~ "^/internal" {
url.access-deny = ("")
}
}
Blocking Sensitive Files
# Deny access to hidden files and backup files
$HTTP["url"] =~ "/\.|\~$|\.bak$|\.sql$|\.inc$|\.env$" {
url.access-deny = ("")
}
# Prevent access to version control directories
$HTTP["url"] =~ "^/(\.git|\.svn|\.hg)/" {
url.access-deny = ("")
}
HTTP Method Restrictions
# Only allow GET and POST on the main site
$HTTP["host"] == "example.com" {
$HTTP["request-method"] !~ "^(GET|POST|HEAD)$" {
url.access-deny = ("")
}
}
Rate Limiting Basics
While Lighttpd doesn't have built-in rate limiting, you can achieve basic protection using mod_access combined with connection limits:
server.max-connections = 500
server.max-connections-per-ip = 10
For more sophisticated rate limiting, consider placing Lighttpd behind a dedicated load balancer or using firewall rules with iptables or nftables.
Logging and Monitoring
Proper logging is crucial for debugging, security auditing, and performance analysis. Lighttpd provides both access logging and error logging with flexible formatting options.
Access Log Customization
server.modules += ( "mod_accesslog" )
accesslog.format = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O"
accesslog.filename = "/var/log/lighttpd/access.log"
The format string uses Apache-compatible placeholders. The %I and %O tokens log incoming and outgoing byte counts, which are invaluable for bandwidth monitoring.
Conditional Logging
Log different hosts to separate files:
$HTTP["host"] == "example.com" {
accesslog.filename = "/var/log/lighttpd/example.access.log"
}
$HTTP["host"] == "api.example.com" {
accesslog.filename = "/var/log/lighttpd/api.access.log"
}
Error Log Verbosity
server.errorlog = "/var/log/lighttpd/error.log"
debug.log-request-header = "disable"
debug.log-response-header = "disable"
debug.log-file-not-found = "enable"
Enable debug.log-file-not-found during development to track missing assets; disable it in production to reduce log noise.
Performance Tuning
Lighttpd is fast out of the box, but these tuning parameters can help you extract maximum performance for your specific workload.
Connection and Threading Tuning
server.max-connections = 2000
server.max-connections-per-ip = 50
server.max-fds = 4096
server.listen-backlog = 1024
- server.max-connections: The global ceiling on concurrent connections. Set based on available RAM and expected load.
- server.max-connections-per-ip: Prevents a single client from exhausting all connections.
- server.max-fds: Maximum file descriptors. Increase this on high-traffic servers.
- server.listen-backlog: Kernel backlog for pending connections. Raising this helps absorb traffic bursts.
Static File Caching
server.modules += ( "mod_expire" )
$HTTP["url"] =~ "\.(jpg|jpeg|png|gif|ico|css|js|svg|woff2?)$" {
expire.url = ( "" => "access 30 days" )
}
# Cache immutable assets for a year
$HTTP["url"] =~ "\.(webp|avif|woff2)$" {
expire.url = ( "" => "access 365 days" )
}
Compression Tuning
compress.cache-dir = "/var/cache/lighttpd/compress"
compress.filetype = ( "text/html", "text/css", "text/javascript", "application/javascript", "application/json", "text/xml", "image/svg+xml" )
compress.allowed-encodings = ( "gzip", "br" )
Use Brotli (br) alongside gzip for modern browsers; Lighttpd selects the best encoding based on the client's Accept-Encoding header.
Kernel and System Tuning
Beyond Lighttpd's configuration, tune the operating system for high-concurrency serving:
# /etc/sysctl.conf additions
net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_tw_reuse = 1
fs.file-max = 100000
Apply with sudo sysctl -p. Also ensure the www-data user has adequate file descriptor limits in /etc/security/limits.conf.
Containerization and Deployment
Lighttpd is exceptionally well-suited for containerized environments. Its small footprint, fast startup, and single-binary design align perfectly with Docker and Kubernetes patterns.
Minimal Dockerfile
FROM alpine:3.20
RUN apk add --no-cache lighttpd lighttpd-mod_openssl lighttpd-mod_fastcgi
COPY lighttpd.conf /etc/lighttpd/lighttpd.conf
COPY html/ /var/www/html/
EXPOSE 80 443
CMD ["lighttpd", "-D", "-f", "/etc/lighttpd/lighttpd.conf"]
The -D flag tells Lighttpd to run in the foreground (don't daemonize), which is required for container process supervision.
Docker Compose Example with PHP-FPM
version: "3.9"
services:
web:
build: .
ports:
- "80:80"
- "443:443"
volumes:
- ./html:/var/www/html
- ./certs:/etc/lighttpd/certs
depends_on:
- php
php:
image: php:8.3-fpm-alpine
volumes:
- ./html:/var/www/html
expose:
- "9000"
In the Lighttpd configuration, point FastCGI to the PHP service over TCP:
fastcgi.server = (
".php" => ((
"host" => "php",
"port" => 9000,
"broken-scriptfilename" => "enable"
))
)
Best Practices Summary
Over years of production deployments, certain patterns have emerged as consistently reliable. Adopt these practices to build a robust, maintainable Lighttpd setup.
1. Explicit Module Loading
Always define server.modules explicitly rather than relying on defaults. This ensures you know exactly what's running and minimizes the attack surface.
server.modules = (
"mod_indexfile",
"mod_access",
"mod_accesslog",
"mod_staticfile",
"mod_fastcgi",
"mod_openssl",
"mod_h2"
)
2. Split Configuration into Included Files
Use the include directive to organize configuration logically:
include "/etc/lighttpd/conf.d/mime.conf"
include "/etc/lighttpd/conf.d/ssl.conf"
include "/etc/lighttpd/conf.d/vhosts/*.conf"
This makes maintenance easier and allows automated tooling (like Certbot) to drop in configuration snippets safely.
3. Test Configuration Before Reloading
Always validate your configuration syntax before applying changes:
lighttpd -t -f /etc/lighttpd/lighttpd.conf
Only if the test passes should you reload:
sudo systemctl reload lighttpd
4. Run as an Unprivileged User
🚀 Need a reliable AI agent for your project?
Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.
Get Started — $23.99/mo
🚀 Need a reliable AI agent for your project?
Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.
Get Started — $23.99/mo