Slowloris operates by maintaining multiple partial HTTP connections to the target server. Traditional web servers like Apache create a separate thread or process for each connection, exhausting available worker slots with these incomplete requests. Here's a simplified example of how a Slowloris attack might look in Python:
import socket
import time
target = "vulnerable-server.com"
port = 80
for i in range(1000):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.send("GET / HTTP/1.1\r\n".encode())
s.send("Host: {}\r\n".format(target).encode())
while True:
s.send("X-a: b\r\n".encode())
time.sleep(100)
except:
pass
Nginx and Lighttpd fundamentally differ in their concurrency models. Instead of spawning threads/processes per connection, they use:
- Event-driven, asynchronous I/O model (epoll/kqueue)
- Fixed-size worker processes handling thousands of connections
- Connection state machines rather than per-connection resources
This architectural difference means incomplete connections don't consume significant resources. Here's how Nginx typically configures worker connections:
worker_processes auto;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
Both servers implement strict default timeouts that automatically close stalled connections:
# Lighttpd example
server.max-keep-alive-requests = 16
server.max-keep-alive-idle = 5
server.max-read-idle = 60
server.max-write-idle = 360
# Nginx example
client_header_timeout 60s;
client_body_timeout 60s;
keepalive_timeout 75s;
Beyond their core architecture, both web servers offer:
- Rate limiting:
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
- Connection queue limits:
listen 80 backlog=512;
- OS-level protections (sysctl tuning):
net.ipv4.tcp_syncookies = 1 net.core.somaxconn = 4096
You can validate your server's resilience using tools like slowhttptest:
slowhttptest -c 1000 -H -g -o slowhttp -i 10 -r 200 -t GET -u http://yourserver.com -x 24 -p 3
Modern versions of Apache can also achieve similar protection using event MPM and modules like mod_reqtimeout.
Slowloris operates by exploiting a fundamental design characteristic of traditional web servers like Apache. It maintains multiple partial HTTP connections by:
// Typical malicious request pattern
GET / HTTP/1.1\r\n
Host: victim.com\r\n
User-Agent: Slowloris\r\n
Content-Length: 42\r\n
X-a: b\r\n
[artificial delay here]
The attack works because Apache:
- Creates a separate thread/process per connection
- Maintains connection state in memory
- Has fixed limits on concurrent connections
NginX implements several protective measures:
// NginX connection handling pseudocode
while (true) {
events = epoll_wait(epoll_fd, ...);
for each event {
if (event.is_readable) {
process_request(event.fd);
}
}
}
Key architectural advantages:
- Asynchronous, non-blocking I/O model
- Single master process manages multiple worker processes
- Workers handle thousands of connections in event loop
- Configurable timeouts (default 60s):
client_header_timeout
,client_body_timeout
Lighttpd employs several protective mechanisms:
# lighttpd.conf hardening against Slowloris
server.max-keep-alive-requests = 100
server.max-keep-alive-idle = 5
server.max-read-idle = 15
server.max-write-idle = 15
server.max-connections = 2048
Technical differentiators include:
- Fixed-size connection pool with configurable limits
- Aggressive timeout defaults (much shorter than Apache)
- Single-process event loop design
- Built-in connection rate limiting
Server | Architecture | Default Timeout | Connection Handling |
---|---|---|---|
Apache | Process/thread-per-connection | 300s | Synchronous |
NginX | Event-driven | 60s | Asynchronous |
Lighttpd | Single-process event loop | 15-30s | Asynchronous |
For servers vulnerable to Slowloris, consider these mitigation techniques:
# Apache mitigation example
Timeout 30
KeepAliveTimeout 5
MaxClients 256
LimitRequestFields 50
LimitRequestFieldSize 2048
For NginX/Lighttpd administrators, additional hardening:
# NginX additional protection
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 20;
# Rate limiting
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;