# Ferron — fast, memory-safe web server in Rust

> Hands-on guide to Ferron, the memory-safe Rust web server with automatic TLS, HTTP/2 and KDL config — static files, reverse proxy, PHP and security.

Source: https://www.jpkc.com/db/en/cheatsheets/web-servers/ferron/

<!-- PROSE:intro -->
Ferron is a fast, memory-safe web server written entirely in Rust, which rules out whole classes of memory bugs from the start. It handles static files, reverse proxying, PHP over FastCGI, automatic TLS and rate limiting out of the box, with no extra modules to load. Everything is configured through a single, readable KDL file (`ferron.kdl`) that stays far more compact than a classic Apache or NGINX setup. This guide takes you from installation and service management to the key KDL directives for TLS, proxying and security.
<!-- PROSE:intro:end -->

## Installation (Debian/Ubuntu)

Add the official Ferron apt repository and install Ferron on Debian/Ubuntu.

```bash
sudo apt install curl gnupg2 ca-certificates lsb-release
curl https://deb.ferron.sh/signing.pgp | gpg --dearmor | sudo tee /usr/share/keyrings/ferron-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/ferron-keyring.gpg] https://deb.ferron.sh $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ferron.list
sudo apt update && sudo apt install ferron
```

Default file locations after installing Ferron via apt on Debian/Ubuntu.

```bash
# Key paths after apt install:
# Binary:        /usr/sbin/ferron
# Config:        /etc/ferron.kdl
# Web root:      /var/www/ferron
# Logs:          /var/log/ferron/
```

Pull and run Ferron via Docker. Image variants: '2' (distroless), '2-alpine', '2-debian'.

```bash
docker pull ferronserver/ferron:2
docker run --name ferron -d -p 80:80 --restart=always ferronserver/ferron:2
```

## Service Management (systemctl)

`systemctl start ferron` — Start the Ferron web server.

```bash
systemctl start ferron
```

`systemctl stop ferron` — Stop the Ferron web server.

```bash
systemctl stop ferron
```

`systemctl restart ferron` — Restart Ferron (briefly drops connections).

```bash
systemctl restart ferron
```

`systemctl reload ferron` — Reload the Ferron configuration with zero downtime. Re-reads /etc/ferron.kdl.

```bash
systemctl reload ferron
```

`systemctl status ferron` — Show Ferron service status, PID, and recent log output.

```bash
systemctl status ferron
```

`systemctl enable ferron` — Enable Ferron to start automatically on system boot.

```bash
systemctl enable ferron
```

`systemctl disable ferron` — Disable Ferron from starting automatically on boot.

```bash
systemctl disable ferron
```

## CLI Commands

`ferron` — Start Ferron using ferron.kdl in the current directory.

```bash
ferron
```

`ferron -c /etc/ferron.kdl` — Start Ferron with a specific configuration file. Default: ./ferron.kdl

```bash
ferron -c /etc/ferron.kdl
```

`ferron --config-string 'localhost { root "/var/www" }'` — Start Ferron with an inline KDL configuration string instead of a file.

```bash
ferron --config-string 'localhost { root "/var/www" }'
```

`ferron --config-adapter yaml-legacy -c ferron.yaml` — Start Ferron with a legacy Ferron 1.x YAML config file. Adapters: kdl (default), yaml-legacy.

```bash
ferron --config-adapter yaml-legacy -c ferron.yaml
```

`ferron --module-config` — Display the compiled-in module configuration and available modules.

```bash
ferron --module-config
```

`ferron -V` — Show the Ferron version.

```bash
ferron -V
```

`ferron serve` — Quickly serve the current directory over HTTP on port 3000 (no config file needed).

```bash
ferron serve
```

`ferron serve -p 8080 -r /var/www/html` — Serve a specific directory on a custom port.

```bash
ferron serve -p 8080 -r /var/www/html
```

`ferron serve -l 0.0.0.0 -p 80 -r /var/www` — Serve on all interfaces (-l). Default listen address is 127.0.0.1.

```bash
ferron serve -l 0.0.0.0 -p 80 -r /var/www
```

`ferron serve -c "admin:$(ferron-passwd mysecret)"` — Serve with HTTP Basic Auth. Use ferron-passwd to hash the password.

```bash
ferron serve -c "admin:$(ferron-passwd mysecret)"
```

`ferron-passwd <password>` — Generate a hashed password for use with HTTP Basic Auth in ferron serve or ferron.kdl.

```bash
ferron-passwd mysecretpassword
```

`ferron-precompress /var/www/assets` — Pre-compress static assets (gzip/brotli) using 64 threads for faster delivery.

```bash
ferron-precompress /var/www/assets
```

`ferron-precompress -t 8 /var/www/assets` — Pre-compress assets with a specific number of parallel threads.

```bash
ferron-precompress -t 8 /var/www/assets
```

`ferron-yaml2kdl ferron.yaml ferron.kdl` — Migrate a legacy Ferron 1.x YAML config to the modern KDL format.

```bash
ferron-yaml2kdl old-config.yaml ferron.kdl
```

## KDL Config: Structure & Syntax

KDL block targets define which hosts/ports a configuration block applies to. globals{} is evaluated first.

```bash
# Block targets (virtual hosts):
globals { }            # Global settings (applied everywhere)
* { }                  # All hosts/ports
*:80 { }               # All hosts on port 80 only
example.com { }        # Specific domain
"192.168.1.1" { }      # Specific IP address
example.com:8080 { }   # Domain + port
example.com,example.org { }  # Multiple domains
```

KDL directive syntax. Flags are bare keywords; booleans use #true/#false; strings use double quotes.

```bash
# Directive value types:
root "/var/www/html"      # String
timeout 30000             # Integer (ms)
ocsp_stapling             # Boolean flag (presence = true)
directory_listing #false  # Explicit boolean
protocols "h1" "h2"      # Multiple values
```

Location blocks match URL prefixes. remove_base=#true strips the prefix before forwarding to the backend.

```bash
location "/api" remove_base=#true {
    proxy "http://localhost:3000"
}
location "/static" {
    root "/var/www/static"
}
```

Snippets define reusable configuration blocks. 'use' includes a snippet inside any block.

```bash
snippet "COMMON_HEADERS" {
    header "X-Frame-Options" "DENY"
    header "X-Content-Type-Options" "nosniff"
}

example.com {
    use "COMMON_HEADERS"
    root "/var/www/html"
}
```

`include "/etc/ferron.d/**/*.kdl"` — Include additional KDL config files via glob pattern. Merged into the main config.

```bash
include "/etc/ferron.d/**/*.kdl"
```

## Static File Serving

Minimal static file server: serve files from the given directory for a domain.

```bash
example.com {
    root "/var/www/example.com"
}
```

Static site with ETag support, pre-compressed file serving, and aggressive browser caching.

```bash
example.com {
    root "/var/www/html"
    etag
    compressed
    directory_listing #false
    file_cache_control "public, max-age=31536000"
}
```

`compressed` — Serve pre-compressed .br and .gz files automatically when the client supports it (use ferron-precompress to generate them).

```bash
compressed
```

`directory_listing #true` — Enable directory listing (disabled by default).

```bash
directory_listing #true
```

`file_cache_control "public, max-age=3600"` — Set the Cache-Control header for all static file responses.

```bash
file_cache_control "public, max-age=86400"
```

## TLS / HTTPS

HTTPS with manual certificate files: path to certificate and private key.

```bash
example.com {
    tls "/etc/ssl/certs/example.com.crt" "/etc/ssl/private/example.com.key"
    root "/var/www/html"
}
```

Automatic TLS via Let's Encrypt (HTTP-01 challenge). Ferron manages certificate acquisition and renewal.

```bash
example.com {
    auto_tls
    auto_tls_contact "admin@example.com"
    root "/var/www/html"
}
```

On-demand TLS: acquire certificates for any domain on first request (useful for wildcard/multi-tenant setups).

```bash
globals {
    auto_tls_on_demand #true
    auto_tls_contact "admin@example.com"
}
```

Global TLS hardening: minimum version, cipher suite, and OCSP stapling.

```bash
globals {
    tls_min_version "TLSv1.2"
    tls_cipher_suite "TLS_AES_256_GCM_SHA384"
    ocsp_stapling
}
```

Enable HTTP/1.1 and HTTP/2. Add "h3" for experimental HTTP/3 support.

```bash
globals {
    protocols "h1" "h2"
}
```

## Reverse Proxy & Load Balancing

Basic reverse proxy: forward all requests to a backend. WebSocket support is included automatically.

```bash
example.com {
    proxy "http://localhost:3000"
}
```

Reverse proxy to a Unix socket backend. The URL sets the Host header; unix= sets the connection target.

```bash
example.com {
    proxy "http://localhost:3000" unix="/run/app/web.sock"
}
```

Load balance across multiple backends (round-robin by default). lb_health_check enables passive health checks.

```bash
example.com {
    proxy "http://backend1:8080"
    proxy "http://backend2:8080"
    proxy "http://backend3:8080"
    lb_health_check
}
```

`lb_algorithm "round_robin"` — Set the load balancing algorithm. Options: round_robin (default), random, ip_hash.

```bash
lb_algorithm "ip_hash"
```

`proxy_keepalive` — Enable keep-alive connections to the backend for improved performance.

```bash
proxy_keepalive
```

`proxy_http2_only` — Force HTTP/2-only proxying to the backend. Required for gRPC backends.

```bash
proxy_http2_only
```

`proxy_request_header_replace "Host" "{header:Host}"` — Pass the original client Host header to the backend (Ferron replaces it by default).

```bash
proxy_request_header_replace "Host" "{header:Host}"
```

Mixed setup: proxy /api to a backend, serve everything else as static files.

```bash
example.com {
    location "/api" remove_base=#true {
        proxy "http://localhost:3000"
    }
    location "/" {
        root "/var/www/html"
    }
}
```

Enable in-memory response caching for proxied requests. cache_vary adds cache variation by header.

```bash
cache
cache_max_entries 1024
cache_vary "Accept-Encoding"
```

## PHP / FastCGI

Serve PHP via PHP-FPM using a Unix socket. Requires the fcgi module.

```bash
example.com {
    root "/var/www/html"
    fcgi_responder "unix:/run/php/php8.3-fpm.sock"
}
```

Serve PHP via PHP-FPM using TCP. Use TCP when FPM runs on a different host.

```bash
example.com {
    root "/var/www/html"
    fcgi_responder "127.0.0.1:9000"
}
```

Front-controller pattern for PHP frameworks (Laravel, Symfony, WordPress): route all requests through index.php.

```bash
example.com {
    root "/var/www/html"
    rewrite "^/index\.php/?(.*)$" "/$1" last=#true
    location "/" {
        rewrite "^/(.*)$" "/index.php/$1" file=#false directory=#false last=#true
    }
    fcgi_responder "unix:/run/php/php8.3-fpm.sock"
}
```

## Redirects & Rewrites

Redirect all HTTP traffic to HTTPS with a permanent 301 redirect.

```bash
*:80 {
    return 301 "https://{header:Host}{path_and_query}"
}
```

Redirect www to non-www (or vice versa) with a permanent redirect.

```bash
www.example.com {
    return 301 "https://example.com{path_and_query}"
}
```

`return 302 "/maintenance"` — Temporary redirect all requests to a maintenance page.

```bash
return 302 "/maintenance"
```

`rewrite "^/old/(.*)$" "/new/$1" last=#true` — Rewrite rule with regex capture group. last=#true stops further rewrite processing.

```bash
rewrite "^/old/(.*)$" "/new/$1" last=#true
```

`rewrite "^/(.*)$" "/index.php" file=#false directory=#false last=#true` — Route all requests that don't match a file or directory to index.php (front controller).

```bash
rewrite "^/(.*)$" "/index.php" file=#false directory=#false last=#true
```

Single-Page Application setup: fall back to / (index.html) for all client-side routes.

```bash
# SPA: serve index.html for all non-file requests
location "/" {
    root "/var/www/html"
    rewrite "^/.*" "/" directory=#false file=#false last=#true
}
```

## Security & Headers

Essential security headers. Add inside a server block or snippet.

```bash
header "X-Frame-Options" "DENY"
header "X-Content-Type-Options" "nosniff"
header "Referrer-Policy" "strict-origin-when-cross-origin"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
```

`header "Content-Security-Policy" "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"` — Content Security Policy header. Adjust sources to fit your application.

```bash
header "Content-Security-Policy" "default-src 'self'"
```

Remove headers from responses (hide server information).

```bash
header_remove "X-Powered-By"
header_remove "Server"
```

Block specific IP addresses or CIDR ranges. Blocked clients receive a 403 response.

```bash
block "192.168.1.100"
block "10.0.0.0/8"
```

Allowlist IP ranges. Use in combination with block to restrict access to specific networks.

```bash
allow "10.0.0.0/8"
allow "192.168.1.0/24"
```

`trust_x_forwarded_for` — Trust the X-Forwarded-For header from upstream proxies/load balancers for accurate client IP logging.

```bash
trust_x_forwarded_for
```

## Rate Limiting

Limit to 100 requests/second per client IP, allowing bursts up to 200. Excess requests receive 429.

```bash
example.com {
    limit rate=100 burst=200
}
```

Apply tighter rate limiting to a specific path (e.g. API endpoints).

```bash
location "/api" {
    limit rate=10 burst=20
    proxy "http://localhost:3000"
}
```

## Conditions & Conditionals

Named condition block with is_regex. Use 'if' or 'if_not' to apply directives conditionally.

```bash
condition "IS_API" {
    is_regex "{path}" "^/api/"
}

example.com {
    if "IS_API" {
        proxy "http://localhost:3000"
    }
}
```

is_equal checks an exact match on a placeholder value. Negate with if_not.

```bash
condition "NO_BOT" {
    is_equal "{header:User-Agent}" "curl"
}
example.com {
    if_not "NO_BOT" {
        root "/var/www/html"
    }
}
```

is_remote_ip matches the client IP address.

```bash
condition "LOCAL" {
    is_remote_ip "127.0.0.1"
}
```

Placeholders usable in conditions, header directives, return, and rewrite targets.

```bash
# Available placeholders for conditions and headers:
# {path}             - Request URI path
# {path_and_query}   - Path including query string
# {method}           - HTTP method (GET, POST, ...)
# {header:<name>}    - Request header value
# {client_ip}        - Client IP address
# {scheme}           - http or https
```

## Logging & Observability

Set global access and error log file paths.

```bash
globals {
    log "/var/log/ferron/access.log"
    error_log "/var/log/ferron/error.log"
}
```

`log "/var/log/ferron/example-access.log"` — Override log file per virtual host.

```bash
log "/var/log/ferron/example-access.log"
```

`log_format "{client_ip} - [{timestamp}] \"{method} {path}\" {status} {bytes_sent}"` — Customize the access log format using placeholders.

```bash
log_format "{client_ip} {method} {path} {status}"
```

Log to stdout/stderr instead of files. Useful in Docker and containerized environments.

```bash
globals {
    log "stdout"
    error_log "stderr"
}
```

## Full Config Example

Production-ready example: globals, security snippet, HTTP→HTTPS redirect, HTTPS vhost with static files + API proxy, rate limiting.

```bash
globals {
    protocols "h1" "h2"
    tls_min_version "TLSv1.2"
    ocsp_stapling
    log "/var/log/ferron/access.log"
    error_log "/var/log/ferron/error.log"
}

snippet "SECURITY_HEADERS" {
    header "X-Frame-Options" "DENY"
    header "X-Content-Type-Options" "nosniff"
    header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
    header_remove "Server"
}

*:80 {
    return 301 "https://{header:Host}{path_and_query}"
}

example.com {
    tls "/etc/ssl/certs/example.com.crt" "/etc/ssl/private/example.com.key"
    root "/var/www/example.com"
    use "SECURITY_HEADERS"
    etag
    compressed
    limit rate=200 burst=400

    location "/api" remove_base=#true {
        proxy "http://localhost:3000"
        limit rate=50 burst=100
    }
}
```

<!-- PROSE:outro -->
## Conclusion

Ferron shows that a web server need not trade performance for safety or readability: its Rust foundation delivers memory safety without a garbage collector, while the KDL configuration stays legible even across TLS, reverse proxy and PHP setups. With automatic TLS, load balancing and rate limiting built in, many deployments need no extra tooling at all. As the project is still young and evolving quickly, it pays to check the official documentation before relying on it in production.

## Further Reading

- [Ferron – official website](https://ferron.sh/) – overview, features and downloads
- [Ferron documentation](https://ferron.sh/docs) – configuration reference and guides
- [ferronweb/ferron – GitHub](https://github.com/ferronweb/ferron) – source code and issue tracker
<!-- PROSE:outro:end -->

## Related Commands

- [apache](https://www.jpkc.com/db/en/cheatsheets/web-servers/apache/) – established, modular web server with a large ecosystem
- [caddy](https://www.jpkc.com/db/en/cheatsheets/web-servers/caddy/) – Go web server with automatic TLS and simple configuration
- [certbot](https://www.jpkc.com/db/en/cheatsheets/web-servers/certbot/) – issue and renew Let's Encrypt certificates manually

