Lugh is a high-performance proxy server written in Go that serves as a replacement for nginx with built-in Web Application Firewall (WAF) and rate limiting capabilities.
The name comes rom Irish mythology. Known as Samildánach (“skilled in many arts”), Lugh was a master of all crafts and a fierce protector of the Tuatha Dé Danann. Seen as a solar deity and symbol of strength, leadership, and protection. It is the protector aspect that led to the name being adopted for this project.
- HTTP Proxy: Acts as a reverse proxy for your backend services
- Path-based Routing: Route requests to different backends based on URL paths
- Built-in WAF: Integration with Coraza WAF and OWASP Core Rule Set
- Rate Limiting: Token bucket-based rate limiting for each location
- Custom Rules: Support for custom WAF rules with dynamic reloading
- Header Forwarding: Properly forwards headers like nginx does
$ brew tap uradical/tap
$ brew install lughLugh uses a YAML configuration file, by default located at /etc/lugh/config.yaml. You can specify a custom path
using the -config flag.
server:
listen: "0.0.0.0:80"
locations:
- path: "/api"
proxy_pass: "http://api:3000"
rate_limit:
requests_per_second: 20
burst: 20
- path: "/"
proxy_pass: "http://pwa:8080"
rate_limit:
requests_per_second: 50
burst: 100
waf:
enabled: true
custom_rules_path: "/etc/lugh/rules"server.listen: Address and port to listen onlocations: Array of location configurationspath: URL path prefix to matchproxy_pass: Backend server URL to proxy torate_limit.requests_per_second: Maximum requests per secondrate_limit.burst: Maximum burst capacity
waf.enabled: Enable or disable WAFwaf.custom_rules_path: Path to directory containing custom WAF rules
Lugh supports custom WAF rules using the Coraza rule format. Rules should be placed in .conf files in the directory
specified by waf.custom_rules_path.
# Block requests with suspicious User-Agent
SecRule REQUEST_HEADERS:User-Agent "@contains sqlmap" \
"id:10000,\
phase:1,\
deny,\
status:403,\
log,\
msg:'Blocked sqlmap User-Agent',\
severity:CRITICAL"
The custom_rules_path directory is monitored, rule changes are automatically detected and reloaded.
# Start Lugh with default configuration
lugh
# Start Lugh with custom configuration
lugh -config /path/to/config.yamlLugh implements the equivalent functionality of the following nginx configuration:
server {
listen 80;
# Route /api requests to nginx_3000 (the container running on port 3000)
location /api {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://api:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Forward the Origin header
proxy_set_header Origin $http_origin;
# Forward the cookies explicitly
proxy_set_header Cookie $http_cookie;
# Forward all other headers
proxy_pass_request_headers on;
}
# Route all other requests to nginx_8080 (the container running on port 8080)
location / {
limit_req zone=frontend_limit burst=100 nodelay;
proxy_pass http://pwa:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Forward the Origin header
proxy_set_header Origin $http_origin;
# Forward the cookies explicitly
proxy_set_header Cookie $http_cookie;
# Forward all other headers
proxy_pass_request_headers on;
}
}Run the test suite:
go test -vLugh includes comprehensive benchmarks that measure its performance under various conditions. Below are the results from our benchmark tests comparing performance with different payload sizes and WAF configurations.
The chart above demonstrates how response time scales with payload size. As expected, larger payloads take more time to process, but Lugh maintains excellent performance even with 100KB payloads.
| Payload Size | Response Time (ms) |
|---|---|
| Small (0.1KB) | 0.36ms |
| Medium (10KB) | 0.46ms |
| Large (100KB) | 1.35ms |
The WAF impact chart shows a comparison of performance with and without the Web Application Firewall enabled. Interestingly, while WAF adds expected overhead for small payloads, it actually improves performance for medium and large payloads in our tests.
This chart illustrates the percentage impact of enabling WAF. The negative values for medium and large payloads indicate performance improvements rather than overhead.
| Payload Size | Without WAF | With WAF | Overhead (%) |
|---|---|---|---|
| Small | 0.36ms | 0.56ms | +54.9% |
| Medium | 0.46ms | 0.44ms | -3.9% |
| Large | 1.35ms | 0.62ms | -53.7% |
This graph shows the proxy server handling a sustained load of over 300 requests per second with:
- Low latency: Median response time around 2–3 ms, 95th percentile under 10 ms.
- Rate limiting: Proxy enforces limits cleanly, returning
429 Too Many Requestsonce configured thresholds are exceeded. - Stability: 100 concurrent users ramped up smoothly with no signs of instability or performance degradation.
This test was performed on a MacBook Air M2 using Locust for load generation.
Load Generator:
- Tool: Locust v2.x
- Users: 100 concurrent users
- Spawn rate: 10 users/second
- Endpoints tested:
GET /apiandGET / - Traffic pattern: 3:1 ratio of
/apito/
Proxy Configuration:
/api: 20 RPS, burst 20/: 50 RPS, burst 100- WAF: Enabled (Coraza with OWASP Core Rule Set)
- pprof: Enabled on
localhost:6060
Test Machine:
- Device: Apple MacBook Air (M2, 2022)
- CPU: 8-core Apple M2
- RAM: 16 GB unified memory
- OS: macOS Ventura 13.x
These results demonstrate that the proxy performs efficiently under load, with rate limits and WAF enabled — even on a fanless laptop.
These benchmark results show that Lugh is a high-performance proxy server with excellent characteristics:
- Sub-millisecond response times for typical payloads
- Efficient scaling with increasing payload sizes
- Interesting WAF behavior where the WAF actually improves performance for larger payloads
The performance improvement with WAF for medium and especially large payloads could be due to more efficient buffering or processing optimizations in the WAF module. Even in the "slowest" configuration (large payload without WAF), the performance is still excellent at 1.35ms.
To run the benchmarks yourself:
# Run the benchmarks
go test -bench=. -benchtime=5x > benchmark_results.txt
# Generate visualization charts
python generate_charts.pyThe benchmarks measure performance with different payload sizes (small ~100 bytes, medium 10KB, large 100KB) and with/without WAF enabled.
This proxy server includes Go's built-in profiler (pprof) for performance analysis.
-
pprof endpoint:
http://localhost:6060/debug/pprof/ -
Access: Only available locally (not exposed publicly)
-
Usage:
To capture a 30-second CPU profile:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
MIT License
- Coraza WAF - Web Application Firewall engine
- OWASP Core Rule Set - Security rules for detecting common attacks




