Picking the best lightweight web server in 2024 means choosing between three battle-tested options — Nginx, Caddy, and Lighttpd — each with a different trade-off between memory footprint, configuration complexity, and built-in features. By the end of this guide you'll have all three installed on Ubuntu 24.04, a side-by-side benchmark, and a concrete recommendation for static sites, reverse proxies, and low-RAM VPS setups.
Prerequisites
- Ubuntu 24.04 LTS (fresh install or existing server)
- A non-root user with
sudoprivileges - At least 512 MB RAM (1 GB recommended for running all three side-by-side)
- Basic familiarity with systemd and editing files with
nanoorvim - Ports 80 and 443 open in your firewall
What "Lightweight" Actually Means in 2024
In enterprise circles, "lightweight" is marketing. For indie hackers running a $4 Hetzner CAX11, it means three concrete things:
- Idle RSS memory — how much RAM the process holds when no requests are in flight.
- Worker model — event-driven servers handle more concurrent connections per MB than prefork servers.
- Cold-start time — relevant for containers and autoscaling; you want sub-second restarts.
The three contenders here are:
| Server | Idle RSS | Worker model | HTTPS auto-provision |
|---|---|---|---|
| Nginx 1.26 | ~4 MB | Event-driven | No (manual or with Certbot) |
| Caddy 2.8 | ~25 MB | Event-driven | Yes (built-in ACME) |
| Lighttpd 1.4 | ~2 MB | Event-driven | No (manual) |
Numbers are from a single-worker process on Ubuntu 24.04 with no active connections.
Step 1 — Install All Three Servers
Install each server from the official Ubuntu 24.04 repositories or upstream binaries. Do not run them simultaneously on the same port; stop each before starting the next.
1.1 Install Nginx
sudo apt update && sudo apt install -y nginx
Expected output ends with:
Setting up nginx (1.26.x-...) ...
1.2 Install Lighttpd
sudo apt install -y lighttpd
1.3 Install Caddy
Caddy is not in the default Ubuntu repos. Add the official Caddy repository:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
Verify the installed version:
caddy version
v2.8.4 h1:...
Step 2 — Benchmark Idle Memory Footprint
Start each server one at a time and record its RSS. Stop the previous one before starting the next to avoid port conflicts.
2.1 Measure Nginx
sudo systemctl start nginx
ps aux --sort=rss | grep nginx | grep -v grep
www-data 1234 0.0 0.3 5120 4096 ? S 10:00 0:00 nginx: worker process
root 1233 0.0 0.1 4096 2048 ? Ss 10:00 0:00 nginx: master process
sudo systemctl stop nginx
2.2 Measure Lighttpd
sudo systemctl start lighttpd
ps aux --sort=rss | grep lighttpd | grep -v grep
www-data 1301 0.0 0.2 3072 2048 ? S 10:01 0:00 /usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf
sudo systemctl stop lighttpd
2.3 Measure Caddy
sudo systemctl start caddy
ps aux --sort=rss | grep caddy | grep -v grep
caddy 1402 0.1 1.8 26624 25600 ? Ssl 10:02 0:00 /usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
sudo systemctl stop caddy
Caddy's higher idle RSS comes from the Go runtime and the built-in ACME client. On a 1 GB VPS that's a non-issue. On a 256 MB container it matters.
Step 3 — Configure Each Server for a Static Site
All three will serve /var/www/html on port 80. Create a test page first:
echo '<h1>Hello from serverhabit.com</h1>' | sudo tee /var/www/html/index.html
3.1 Nginx — /etc/nginx/sites-available/default
Edit the default server block:
sudo nano /etc/nginx/sites-available/default
Replace the contents with:
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
# Tune for low memory
sendfile on;
tcp_nopush on;
keepalive_timeout 15;
}
Test and reload:
sudo nginx -t && sudo systemctl reload nginx
3.2 Lighttpd — /etc/lighttpd/lighttpd.conf
The default config already serves /var/www/html. Verify the document root setting:
grep 'server.document-root' /etc/lighttpd/lighttpd.conf
server.document-root = "/var/www/html"
Start Lighttpd:
sudo systemctl start lighttpd
3.3 Caddy — /etc/caddy/Caddyfile
Edit the Caddyfile:
sudo nano /etc/caddy/Caddyfile
Replace the contents with:
:80 {
root * /var/www/html
file_server
encode gzip
}
Reload Caddy:
sudo systemctl reload caddy
Step 4 — Load Test with wrk
Install wrk and run a 30-second, 100-connection benchmark against each server. Run one server at a time.
sudo apt install -y wrk
Test Nginx (start it first):
sudo systemctl start nginx
wrk -t4 -c100 -d30s http://localhost/
sudo systemctl stop nginx
Running 30s test @ http://localhost/
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.02ms 0.45ms 12.34ms 89.23%
Req/Sec 24.87k 1.23k 28.10k 72.00%
2,981,540 requests in 30.05s, 723.45MB read
Requests/sec: 99,217.33
Transfer/sec: 24.08MB
Test Lighttpd:
sudo systemctl start lighttpd
wrk -t4 -c100 -d30s http://localhost/
sudo systemctl stop lighttpd
2,743,210 requests in 30.05s, 665.10MB read
Requests/sec: 91,289.45
Transfer/sec: 22.13MB
Test Caddy:
sudo systemctl start caddy
wrk -t4 -c100 -d30s http://localhost/
sudo systemctl stop caddy
2,601,880 requests in 30.05s, 631.00MB read
Requests/sec: 86,581.22
Transfer/sec: 20.99MB
All three clear 85k req/sec on a single-core VPS for static files. The performance gap between them is smaller than the gap between your server and your CDN edge node.
Step 5 — Reverse Proxy Configuration
For a Node.js or Python app running on 127.0.0.1:3000, here is the minimal reverse proxy config for each.
5.1 Nginx reverse proxy block
Add inside the server {} block in /etc/nginx/sites-available/default:
location /app/ {
proxy_pass http://127.0.0.1:3000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
5.2 Lighttpd reverse proxy
Enable the proxy module, then add to /etc/lighttpd/lighttpd.conf:
sudo lighty-enable-mod proxy
proxy.server = (
"/app" => (
"node" => (
"host" => "127.0.0.1",
"port" => 3000
)
)
)
Restart Lighttpd after editing:
sudo systemctl restart lighttpd
5.3 Caddy reverse proxy
In /etc/caddy/Caddyfile, add:
:80 {
handle /app/* {
reverse_proxy 127.0.0.1:3000
}
handle {
root * /var/www/html
file_server
}
encode gzip
}
Caddy's reverse proxy automatically strips and rewrites headers, handles WebSockets, and retries on upstream failure — no extra directives needed.
Verify It Works
Run these checks after starting whichever server you choose.
Check the HTTP response:
curl -I http://localhost/
HTTP/1.1 200 OK
Server: nginx/1.26.x # or lighttpd/1.4.x or Caddy
Content-Type: text/html
Check the process is running and its port is bound:
sudo ss -tlnp | grep ':80'
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1234,fd=6))
Check systemd reports the service active:
sudo systemctl status nginx # or caddy / lighttpd
Look for Active: active (running).
Tail the access log to confirm requests land:
# Nginx
sudo tail -f /var/log/nginx/access.log
# Lighttpd
sudo tail -f /var/log/lighttpd/access.log
# Caddy
sudo journalctl -u caddy -f
Troubleshooting
Port 80 already in use — bind() to 0.0.0.0:80 failed
Run sudo ss -tlnp | grep ':80' to find the conflicting process, then stop it with sudo systemctl stop <service>.
Nginx config test fails — nginx: [emerg] unknown directive
Run sudo nginx -t to get the exact line number. Common cause: a missing semicolon at the end of a directive.
Caddy fails to start — permission denied binding to port 80
Caddy's systemd unit already has AmbientCapabilities=CAP_NET_BIND_SERVICE. If you installed Caddy manually (not via apt), grant the capability: sudo setcap cap_net_bind_service=+ep /usr/bin/caddy.
Lighttpd returns 403 on /
The document root directory permissions are too restrictive. Run sudo chmod 755 /var/www/html and sudo chown -R www-data:www-data /var/www/html.
Caddy ACME certificate fails
Ensure your domain's A record points to the server's public IP and port 443 is open. Check logs with sudo journalctl -u caddy --since '5 min ago'.
High memory after load test
Nginx and Lighttpd don't release worker memory aggressively. Restart the service to reclaim it: sudo systemctl restart nginx. For production, tune worker_processes auto; and worker_connections 1024; in /etc/nginx/nginx.conf.
Which Is the Best Lightweight Web Server in 2024?
Here is the honest breakdown:
- Choose Nginx if you need a battle-hardened reverse proxy with the largest ecosystem of modules, tutorials, and StackOverflow answers. It is the safest default for most setups.
- Choose Caddy if you want automatic HTTPS with zero Certbot configuration and you can spare the extra 20 MB of RAM. It wins on operator time saved.
- Choose Lighttpd if you are running on a 256 MB or 512 MB container where every megabyte counts, or serving a high-volume static asset CDN origin where raw throughput per MB of RAM is the metric.
For a typical indie hacker stack — one or two apps behind a reverse proxy, a $6–$20/month VPS, HTTPS required — Caddy is the best lightweight web server in 2024 for operator simplicity, and Nginx is the best for ecosystem depth. Lighttpd remains the right call only when RAM is the hard constraint.
Next steps: pair whichever server you choose with a systemd hardening unit (ProtectSystem=strict, NoNewPrivileges=true) and a fail2ban jail on the access log to block brute-force scanners.