KDL-format configuration file (often named ferron.kdl
). Below are the descriptions of configuration properties for this server.
At the top level of the server configration, the confguration blocks representing specific virtual host are specified. Below are the examples of such configuration blocks:
globals {
// Global configuration that doesn't imply any virtual host (Ferron 2.0.0-beta.13 or newer)
}
* {
// Global configuration
}
*:80 {
// Configuration for port 80
}
example.com {
// Configuration for "example.com" virtual host
}
"192.168.1.1" {
// Configuration for "192.168.1.1" IP virtual host
}
example.com:8080 {
// Configuration for "example.com" virtual host with port 8080
}
"192.168.1.1:8080" {
// Configuration for "192.168.1.1" IP virtual host with port 8080
}
api.example.com {
// Below is the location configuration for paths beginning with "/v1/". If there was "remove_base=#true", the request URL for the location would be rewritten to remove the base URL
location "/v1" remove_base=#false {
// ...
// Below is the error handler configuration for any status code.
error_config {
// ...
}
}
// In Ferron 2.0.0-beta.14 and earlier, the location configuration order was important; in this host configuration, first the "/v1" location is checked, then the "/" location.
// In Ferron 2.0.0-beta.15 and newer, the location and conditionals' configuration order is automatically determined based on the location and conditionals' depth
location "/" {
// ...
}
// Below is the error handler configuration for 404 Not Found status code. If "404" wasn't included, it would be for all errors.
error_config 404 {
// ...
}
}
example.com,example.org {
// Configuration for example.com and example.org (Ferron 2.0.0-beta.13 or newer)
// The virtual host identifiers (like example.com or "192.168.1.1") are comma-separated, but adding spaces will not be interpreted,
// For example "example.com, example.org" will not work for "example.org", but "example.com,example.org" will work.
}
with-conditions.example.com {
condition "SOME_CONDITION" {
// Here are defined subconditions in the condition. The condition will pass if all subconditions will also pass
}
if "SOME_CONDITION" {
// Conditional configuration (Ferron 2.0.0-beta.15 or newer)
// Conditions can be nested
}
if_not "SOME_CONDITION" {
// Configuration, in case of condition not being met (Ferron 2.0.0-beta.15 or newer)
}
}
snippet "EXAMPLE" {
// Example snippet configuration (Ferron 2.0.0-beta.15 or newer)
}
with-snippet.example.com {
// Import from snippet (Ferron 2.0.0-beta.15 or newer)
use "EXAMPLE"
}
with-snippet.example.org {
// Snippets can be reusable
use "EXAMPLE"
}
inheritance.example.com {
// The "proxy" directive is used as an example for demonstrating inheritance.
proxy "http://10.0.0.2:3000"
proxy "http://10.0.0.3:3000"
// Here, these directives take effect:
// proxy "http://10.0.0.2:3000"
// proxy "http://10.0.0.3:3000"
location "/somelocation" {
// Here, `some_directive` directives are inherited from the parent block.
// These directives take effect:
// proxy "http://10.0.0.2:3000"
// proxy "http://10.0.0.3:3000"
}
location "/anotherlocation" {
// The directives from the parent block are not inherited if there are other directives with the same name in the block.
// Here, these directives take effect:
// proxy "http://10.0.0.4:3000"
proxy "http://10.0.0.4:3000"
}
}
Also, it’s possible to include other configuration files using an include <included_configuration_path: string>
directive, like this:
include "/etc/ferron.d/**/*.kdl"
This configuration reference organizes directives by both scope (where they can be used) and functional categories (what they do). This makes it easier to find the directives you need based on your specific requirements.
tls_cipher_suite <tls_cipher_suite: string> [<tls_cipher_suite_2: string> ...]
tls_ecdh_curves <ecdh_curve: string> [<ecdh_curve: string> ...]
tls_client_certificate [enable_tls_client_certificate: bool]
tls_client_certificate #false
ocsp_stapling [enable_ocsp_stapling: bool]
ocsp_stapling #true
block <blocked_ip: string> [<blocked_ip: string> ...]
allow <allowed_ip: string> [<allowed_ip: string> ...]
(Ferron 2.0.0-beta.9 or newer)auto_tls_on_demand_ask <auto_tls_on_demand_ask_url: string|null>
(Ferron 2.0.0-beta.13 or newer)domain
query parameter with the domain name for the certificate to issue as a value to the URL. It’s recommended to configure this option when using automatic TLS on demand to prevent abuse. Default: auto_tls_on_demand_ask #null
auto_tls_on_demand_ask_no_verification [auto_tls_on_demand_ask_no_verification: bool]
(Ferron 2.0.0-beta.13 or newer)auto_tls_on_demand_ask_no_verification #false
Configuration example:
* {
tls_cipher_suite "TLS_AES_256_GCM_SHA384" "TLS_AES_128_GCM_SHA256"
tls_ecdh_curves "secp256r1" "secp384r1"
tls_client_certificate #false
ocsp_stapling
block "192.168.1.100" "10.0.0.5"
allow "192.168.1.0/24" "10.0.0.0/8"
auto_tls_on_demand_ask "https://auth.example.com/check"
auto_tls_on_demand_ask_no_verification #false
}
default_http_port <default_http_port: integer|null>
default_http_port #null
, the implicit default HTTP port is disabled. Default: default_http_port 80
default_https_port <default_https_port: integer|null>
default_https_port #null
, the implicit default HTTPS port is disabled. Default: default_https_port 443
protocols <protocol: string> [<protocol: string> ...]
"h1"
(HTTP/1.x), "h2"
(HTTP/2) and "h3"
(HTTP/3; experimental). Default: protocols "h1" "h2"
timeout <timeout: integer|null>
timeout #null
, the timeout is disabled. It’s not recommended to disable the timeout, as this might leave the server vulnerable to Slow HTTP attacks. Default: timeout 300000
h2_initial_window_size <h2_initial_window_size: integer>
h2_max_frame_size <h2_max_frame_size: integer>
h2_max_concurrent_streams <h2_max_concurrent_streams: integer>
h2_max_header_list_size <h2_max_header_list_size: integer>
h2_enable_connect_protocol [h2_enable_connect_protocol: bool]
protocol_proxy [enable_proxy_protocol: bool]
(Ferron 2.0.0-beta.10 or newer)protocol_proxy #false
buffer_request <request_buffer_size: integer|null>
(Ferron 2.0.0-beta.14 or newer)buffer_request #null
, the request buffer is disabled. The request buffer can serve as an additional protection for underlying backend servers against Slowloris-style attacks. Default: buffer_request #null
buffer_response <response_buffer_size: integer|null>
(Ferron 2.0.0-beta.14 or newer)buffer_response #null
, the response buffer is disabled. Default: buffer_response #null
Configuration example:
* {
default_http_port 80
default_https_port 443
protocols "h1" "h2" "h3"
timeout 300000
h2_initial_window_size 65536
h2_max_frame_size 16384
h2_max_concurrent_streams 100
h2_max_header_list_size 8192
h2_enable_connect_protocol
protocol_proxy #false
buffer_request #null
buffer_response #null
}
cache_max_entries <cache_max_entries: integer|null>
(cache module)cache_max_entries #null
, the cache can theoretically store an unlimited number of entries. The cache keys for entries depend on the request method, the rewritten request URL, the “Host” header value, and varying request headers. Default: cache_max_entries 1024
Configuration example:
* {
cache_max_entries 2048
}
lb_health_check_window <lb_health_check_window: integer>
(rproxy module)lb_health_check_window 5000
Configuration example:
* {
lb_health_check_window 5000
}
listen_ip <listen_ip: string>
listen_ip "::1"
io_uring [enable_io_uring: bool]
io_uring
is enabled. This directive has no effect for systems that don’t support io_uring
and for web server builds that use Tokio instead of Monoio. Default: io_uring #true
tcp_send_buffer <tcp_send_buffer: integer>
tcp_recv_buffer <tcp_recv_buffer: integer>
Configuration example:
* {
listen_ip "0.0.0.0"
io_uring
tcp_send_buffer 65536
tcp_recv_buffer 65536
}
tls <certificate_path: string> <private_key_path: string>
auto_tls [enable_automatic_tls: bool]
auto_tls #true
when port isn’t explicitly specified and if the hostname doesn’t look like a local address (127.0.0.1
, ::1
, localhost
), otherwise auto_tls #false
auto_tls_contact <auto_tls_contact: string|null>
auto_tls_contact #null
auto_tls_cache <auto_tls_cache: string|null>
/home/user/.local/share/ferron-acme
for the “user” user, on macOS it can be /Users/user/Library/Application Support/ferron-acme
for the “user” user, on Windows it can be C:\Users\user\AppData\Local\ferron-acme
for the “user” user.auto_tls_letsencrypt_production [enable_auto_tls_letsencrypt_production: bool]
auto_tls_letsencrypt_production #false
, the staging Let’s Encrypt ACME endpoint is used. Default: auto_tls_letsencrypt_production #true
auto_tls_challenge <acme_challenge_type: string> [provider=<acme_challenge_provider: string>] [...]
"http-01"
(HTTP-01 ACME challenge), "tls-alpn-01"
(TLS-ALPN-01 ACME challenge) and "dns-01"
(DNS-01 ACME challenge; Ferron 2.0.0-beta.9 or newer). The provider
prop defines the DNS provider to use for DNS-01 challenges. Additional props can be passed as parameters for the DNS provider, see automatic TLS documentation. Default: auto_tls_challenge "tls-alpn-01"
auto_tls_directory <auto_tls_directory: string>
(Ferron 2.0.0-beta.3 or newer)auto_tls_letsencrypt_production
directive. Default: noneauto_tls_no_verification [auto_tls_no_verification: bool]
(Ferron 2.0.0-beta.3 or newer)auto_tls_no_verification #false
auto_tls_profile <auto_tls_profile: string|null>
(Ferron 2.0.0-beta.9 or newer)auto_tls_profile #null
auto_tls_on_demand <auto_tls_on_demand: bool>
(Ferron 2.0.0-beta.13 or newer)auto_tls_on_demand_ask
directive alongside this directive. Default: auto_tls_on_demand #false
auto_tls_eab (<auto_tls_eab_key_id: string> <auto_tls_eab_key_hmac: string>)|<auto_tls_eab_disabled: null>
(Ferron 2.0.0-beta.15 or newer)auto_tls_eab_disabled #null
, the EAB is disabled. Default: auto_tls_eab_disabled #null
Configuration example:
example.com {
auto_tls
auto_tls_contact "admin@example.com"
auto_tls_cache "/var/cache/ferron-acme"
auto_tls_letsencrypt_production
auto_tls_challenge "tls-alpn-01"
auto_tls_profile "default"
auto_tls_on_demand #false
auto_tls_eab #null
}
manual-tls.example.com {
tls "/etc/ssl/certs/example.com.crt" "/etc/ssl/private/example.com.key"
}
log <log_file_path: string>
error_log <error_log_file_path: string>
Configuration example:
example.com {
log "/var/log/ferron/example.com.access.log"
error_log "/var/log/ferron/example.com.error.log"
}
header <header_name: string> <header_value: string>
{path}
which will be replaced with the request path. This directive can be specified multiple times. Default: noneserver_administrator_email <server_administrator_email: string>
error_page <status_code: integer> <path: string>
header_remove <header_name: string>
(Ferron 2.0.0-beta.5 or newer)header_replace <header_name: string> <header_value: string>
(Ferron 2.0.0-beta.9 or newer){path}
which will be replaced with the request path. This directive can be specified multiple times. Default: noneConfiguration example:
example.com {
header "X-Frame-Options" "DENY"
header "X-Content-Type-Options" "nosniff"
header "X-XSS-Protection" "1; mode=block"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
header "X-Custom-Header" "Custom value with {path} placeholder"
header_remove "X-Header-To-Remove"
header_replace "X-Powered-By" "Ferron"
server_administrator_email "admin@example.com"
error_page 404 "/var/www/errors/404.html"
error_page 500 "/var/www/errors/500.html"
}
trust_x_forwarded_for [trust_x_forwarded_for: bool]
X-Forwarded-For
header. It’s recommended to configure this directive if behind a reverse proxy. Default: trust_x_forwarded_for #false
status <status_code: integer> [url=<url: string>|regex=<regex: string>] [location=<location: string>] [realm=<realm: string>] [brute_protection=<enable_brute_protection: bool>] [users=<users: string>] [allowed=<allowed: string>] [not_allowed=<not_allowed: string>] [body=<response_body: string>]
url
prop specifies the request path for this status code. The regex
prop specifies the regular expression (like ^/ferron(?:$|[/#?])
) for the custom status code. The location
prop specifies the destination for the redirect; it supports placeholders (on Ferron 2.0.0-beta.15 and newer) like {path}
which will be replaced with the request path. The realm
prop specifies the HTTP basic authentication realm. The brute_protection
prop specifies whenever the brute-force protection is enabled. The users
prop is a comma-separated list of allowed users for HTTP authentication. The allowed
prop is a comma-separated list of IP addresses applicable for the status code. The not_allowed
prop is a comma-separated list of IP addresses not applicable for the status code. The body
prop (Ferron 2.0.0-beta.5 or newer) specifies the response body to be sent. Default: noneuser [username: string] [password_hash: string]
scrypt
one). It’s recommended to use the ferron-passwd
tool to generate the password hash. This directive can be specified multiple times. Default: noneConfiguration example:
example.com {
trust_x_forwarded_for
// Basic authentication with custom status codes
status 401 url="/admin" realm="Admin Area" users="admin,moderator"
status 403 url="/restricted" allowed="192.168.1.0/24" body="Access denied"
status 301 url="/old-page" location="/new-page"
// User definitions for authentication (use `ferron-passwd` to generate password hashes)
users "admin" "$2b$10$hashedpassword12345"
users "moderator" "$2b$10$anotherhashedpassword"
}
allow_double_slashes [allow_double_slashes: bool]
allow_double_slashes #false
no_redirect_to_https [no_redirect_to_https: bool]
no_redirect_to_https
when the server port is explicitly specified in the configuration. Default: no_redirect_to_https #false
wwwredirect [enable_wwwredirect: bool]
rewrite <regex: string> <replacement: string> [directory=<directory: bool>] [file=<file: bool>] [last=<last: bool>] [allow_double_slashes=<allow_double_slashes: bool>]
^/ferron(?:$|[/#?])
). The directory
prop specifies whenever the rewrite rule is applied when the path would correspond to directory (if #false
, then it’s not applied). The file
prop specifies whenever the rewrite rule is applied when the path would correspond to file (if #false
, then it’s not applied). The last
prop specifies whenever the rewrite rule is the last rule applied. The allow_double_slashes
prop specifies whenever the rewrite rule allows double slashes in the request URL. Default: nonerewrite_log [rewrite_log: bool]
rewrite_log #false
no_trailing_redirect [no_trailing_redirect: bool]
no_trailing_redirect #false
Configuration example:
example.com {
allow_double_slashes #false
no_redirect_to_https #false
wwwredirect #false
// URL rewriting examples
rewrite "^/old-path/(.*)" "/new-path/$1" last=#true
rewrite "^/api/v1/(.*)" "/api/v2/$1" file=#false directory=#false
rewrite "^/blog/([^/]+)/?(?:$|[?#])" "/blog.php?slug=$1" last=#true
rewrite_log
no_trailing_redirect #false
}
root <webroot: string|null>
root #null
, the static file serving functionality is disabled. Default: noneetag [enable_etag: bool]
(static module)etag #true
compressed [enable_compression: bool]
(static module)compressed #true
directory_listing [enable_directory_listing: bool]
(static module)directory_listing #false
Configuration example:
example.com {
root "/var/www/example.com"
etag
compressed
directory_listing #false
// Set "Cache-Control" header for static files
file_cache_control "public, max-age=3600"
}
cache [enable_cache: bool]
(cache module)cache #false
cache_max_response_size <cache_max_response_size: integer|null>
(cache module)cache_max_response_size #null
, the cache can theoretically store responses of any size. Default: cache_max_response_size 2097152
cache_vary <varying_request_header: string> [<varying_request_header: string> ...]
(cache module)cache_ignore <ignored_response_header: string> [<ignored_response_header: string> ...]
(cache module)file_cache_control <cache_control: string|null>
(static module; Ferron 2.0.0-beta.9 or newer)file_cache_control #null
, the Cache-Control header is not set. Default: file_cache_control #null
Configuration example:
example.com {
cache
cache_max_response_size 2097152
cache_vary "Accept-Encoding" "Accept-Language"
cache_ignore "Set-Cookie" "Cache-Control"
}
proxy <proxy_to: string|null>
(rproxy module)lb_health_check [enable_lb_health_check: bool]
(rproxy module)lb_health_check #false
lb_health_check_max_fails <max_fails: integer>
(rproxy module)lb_health_check_max_fails 3
proxy_no_verification [proxy_no_verification: bool]
(rproxy module)proxy_no_verification #false
proxy_intercept_errors [proxy_intercept_errors: bool]
(rproxy module)proxy_intercept_errors #false
proxy_request_header <header_name: string> <header_value: string>
(rproxy module; Ferron 2.0.0-beta.5 or newer){path}
which will be replaced with the request path. This directive can be specified multiple times. Default: noneproxy_request_header_remove <header_name: string>
(rproxy module; Ferron 2.0.0-beta.5 or newer)proxy_keepalive [proxy_keepalive: bool]
(rproxy module; Ferron 2.0.0-beta.5 or newer)proxy_keepalive #true
proxy_request_header_replace <header_name: string> <header_value: string>
(rproxy module; Ferron 2.0.0-beta.9 or newer){path}
which will be replaced with the request path. This directive can be specified multiple times. Default: noneproxy_http2 [enable_proxy_http2: bool]
(rproxy module; Ferron 2.0.0-beta.13 or newer)proxy_http2 #false
lb_retry_connection [enable_lb_retry_connection: bool]
(rproxy module; Ferron 2.0.0-beta.15 or newer)lb_retry_connection #true
Configuration example:
api.example.com {
// Backends for load balancing
// (or you can also use a single backend by specifying only one `proxy` directive)
proxy "http://backend1:8080"
proxy "http://backend2:8080"
proxy "http://backend3:8080"
// Health check configuration
lb_health_check
lb_health_check_max_fails 3
// Proxy settings
proxy_no_verification #false
proxy_intercept_errors #false
proxy_keepalive
proxy_http2 #false
// Proxy headers
proxy_request_header "X-Custom-Header" "CustomValue"
proxy_request_header_remove "X-Internal-Token"
proxy_request_header_replace "X-Real-IP" "{client_ip}"
}
forward_proxy [enable_forward_proxy: bool]
(fproxy module)forward_proxy #false
Configuration example:
* {
forward_proxy
}
auth_to <auth_to: string|null>
(fauth module)auth_to_no_verification [auth_to_no_verification: bool]
(fauth module)auth_to_no_verification #false
auth_to_copy <request_header_to_copy: string> [<request_header_to_copy: string> ...]
(fauth module)Configuration example:
app.example.com {
// Forward authentication to external service
auth_to "https://auth.example.com/validate"
auth_to_no_verification #false
auth_to_copy "Authorization" "X-User-Token" "X-Session-ID"
}
cgi [enable_cgi: bool]
(cgi module)cgi #false
cgi_extension <cgi_extension: string|null>
(cgi module)cgi-bin
directory. This directive can be specified multiple times. Default: nonecgi_interpreter <cgi_extension: string> <cgi_interpreter: string|null> [<cgi_interpreter_argument: string> ...]
(cgi module)#null
, the default interpreter settings will be disabled. This directive can be specified multiple times. Default: specified for .pl
, .py
, .sh
, .ksh
, .csh
, .rb
and .php
extensions, and additionally .exe
, .bat
and .vbs
extensions for Windowscgi_environment <environment_variable_name: string> <environment_variable_value: string>
(cgi module)scgi <scgi_to: string|null>
(scgi module)tcp://localhost:4000/
) and Unix socket URLs (only on Unix systems; for example unix:///run/scgi.sock
) are supported. Default: scgi #null
scgi_environment <environment_variable_name: string> <environment_variable_value: string>
(scgi module)fcgi <fcgi_to: string|null> [pass=<fcgi_pass: bool>]
(fcgi module)pass
prop specified whenever to pass the all the requests to the FastCGI request handler. TCP (for example tcp://localhost:4000/
) and Unix socket URLs (only on Unix systems; for example unix:///run/scgi.sock
) are supported. Default: fcgi #null pass=#true
fcgi_php <fcgi_php_to: string|null>
(fcgi module)tcp://localhost:4000/
) and Unix socket URLs (only on Unix systems; for example unix:///run/scgi.sock
) are supported. Default: fcgi_php #null
fcgi_extension <fcgi_extension: string|null>
(fcgi module)fcgi_environment <environment_variable_name: string> <environment_variable_value: string>
(fcgi module)Configuration example:
cgi.example.com {
// CGI configuration
cgi
cgi_extension ".cgi" ".pl" ".py"
cgi_interpreter ".py" "/usr/bin/python3"
cgi_interpreter ".pl" "/usr/bin/perl"
cgi_environment "PATH" "/usr/bin:/bin"
cgi_environment "SCRIPT_ROOT" "/var/www/cgi-bin"
}
scgi.example.com {
// SCGI configuration
scgi "tcp://localhost:4000/"
scgi_environment "SCRIPT_NAME" "/app"
scgi_environment "SERVER_NAME" "example.com"
}
fastcgi.example.com {
// FastCGI configuration
fcgi "tcp://localhost:9000/" pass=#true
fcgi_php "tcp://localhost:9000/"
fcgi_extension ".php" ".php5"
fcgi_environment "SCRIPT_FILENAME" "/var/www/example.com{path}"
fcgi_environment "DOCUMENT_ROOT" "/var/www/example.com"
}
replace <searched_string: string> <replaced_string: string> [once=<replace_once: bool>]
(replace module; Ferron 2.0.0-beta.2 or newer)once
prop specifies whenever the string will be replaced once, by default this prop is set to #true
. Default: nonereplace_last_modified [preserve_last_modified: bool]
(replace module; Ferron 2.0.0-beta.2 or newer)replace_last_modified #false
replace_filter_types <filter_type: string> [<filter_type: string> ...]
(replace module; Ferron 2.0.0-beta.2 or newer)text/html
) or a wildcard (*
) specifying that responses with all MIME types are processed for replacement. This directive can be specified multiple times. Default: replace_filter_types "text/html"
Configuration example:
example.com {
// Disabling HTTP compression is required for string replacement
compressed #false
// String replacement in response bodies (works with HTTP compression disabled)
replace "old-company-name" "new-company-name" once=#false
replace "http://old-domain.com" "https://new-domain.com" once=#true
replace_last_modified
replace_filter_types "text/html" "text/css" "application/javascript"
}
limit [enable_limit: bool] [rate=<rate: integer|float>] [burst=<rate: integer|float>]
(limit module; Ferron 2.0.0-beta.2 or newer)rate
prop specifies the maximum average amount of requests per second, defaults to 25 requests per second. The burst
prop specifies the maximum peak amount of requests per second, defaults to 4 times the maximum average amount of requests per second. Default: limit #false
Configuration example:
example.com {
// Global rate limiting
limit rate=100 burst=200
// Different rate limits for different paths
location "/api" {
limit rate=10 burst=20
}
location "/login" {
limit rate=5 burst=10
}
}
Ferron 2.0.0-beta.15 and newer supports conditional configuration based on conditions. This allows you to configure different settings based on the request method, path, or other conditions.
Below is the list of supported subconditions:
is_remote_ip <remote_ip: string> [<remote_ip: string> ...]
(Ferron 2.0.0-beta.15 or newer)is_forwarded_for <remote_ip: string> [<remote_ip: string> ...]
(Ferron 2.0.0-beta.15 or newer)X-Forwarded-For
header) is coming from a specific forwarded IP address or a list of IP addresses.is_not_remote_ip <remote_ip: string> [<remote_ip: string> ...]
(Ferron 2.0.0-beta.15 or newer)is_not_forwarded_for <remote_ip: string> [<remote_ip: string> ...]
(Ferron 2.0.0-beta.15 or newer)X-Forwarded-For
header) is not coming from a specific forwarded IP address or a list of IP addresses.is_equal <left_side: string> <right_side: string>
(Ferron 2.0.0-beta.15 or newer)is_not_equal <left_side: string> <right_side: string>
(Ferron 2.0.0-beta.15 or newer)is_regex <value: string> <regex: string> [case_insensitive=<case_insensitive: bool>]
(Ferron 2.0.0-beta.15 or newer)case_insensitive
prop specifies whether the regex should be case insensitive (#false
by default).is_not_regex <value: string> <regex: string> [case_insensitive=<case_insensitive: bool>]
(Ferron 2.0.0-beta.15 or newer)case_insensitive
prop specifies whether the regex should be case insensitive (#false
by default).Ferron supports the following placeholders for header values, subconditions, reverse proxying, and redirect destinations:
{path}
- the request URI with path and query string (for example, /index.html?param=value
){method}
(Ferron 2.0.0-beta.9 or newer) - the request method{version}
(Ferron 2.0.0-beta.9 or newer) - the HTTP version of the request{header:<header_name>}
(Ferron 2.0.0-beta.9 or newer) - the header value of the request URI{scheme}
(Ferron 2.0.0-beta.9 or newer) - the scheme of the request URI (http
or https
), applicable only for subconditions, reverse proxying and redirect destinations.{client_ip}
(Ferron 2.0.0-beta.9 or newer) - the client IP address, applicable only for subconditions, reverse proxying and redirect destinations.{client_port}
(Ferron 2.0.0-beta.9 or newer) - the client port number, applicable only for subconditions, reverse proxying and redirect destinations.{server_ip}
(Ferron 2.0.0-beta.9 or newer) - the server IP address, applicable only for subconditions, reverse proxying and redirect destinations.{server_port}
(Ferron 2.0.0-beta.9 or newer) - the server port number, applicable only for subconditions, reverse proxying and redirect destinations.Below is an example of Ferron configuration involving location blocks:
example.com {
root "/var/www/example.com"
// Static assets with different settings
location "/static" remove_base=#true {
root "/var/www/static"
compressed
file_cache_control "public, max-age=31536000"
}
// API endpoints with proxy
location "/api" {
proxy "http://backend:8080"
proxy_request_header "X-Forwarded-For" "{client_ip}"
limit rate=50 burst=100
}
// Admin area with authentication
location "/admin" {
status 401 realm="Admin Access" users="admin"
root "/var/www/admin"
}
// PHP files
fcgi_php "tcp://localhost:9000/"
}
Below is a complete example of Ferron configuration, combining multiple sections:
// Global configuration
* {
// Protocol and performance settings
protocols "h1" "h2"
h2_initial_window_size 65536
h2_max_concurrent_streams 100
timeout 300000
// Network settings
listen_ip "0.0.0.0"
default_http_port 80
default_https_port 443
// Security defaults
tls_cipher_suite "TLS_AES_256_GCM_SHA384" "TLS_AES_128_GCM_SHA256"
ocsp_stapling
block "192.168.1.100"
// Global caching
cache_max_entries 1024
// Load balancing settings
lb_health_check_window 5000
// Logging
log "/var/log/ferron/access.log"
error_log "/var/log/ferron/error.log"
// Static file defaults
compressed
etag
}
// Define reusable snippets
snippet "security_headers" {
// Common security headers
header "X-Frame-Options" "DENY"
header "X-Content-Type-Options" "nosniff"
header "X-XSS-Protection" "1; mode=block"
header "Referrer-Policy" "strict-origin-when-cross-origin"
}
snippet "cors_headers" {
// CORS headers for API endpoints
header "Access-Control-Allow-Origin" "*"
header "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS"
header "Access-Control-Allow-Headers" "Authorization, Content-Type, X-Requested-With"
header "Access-Control-Max-Age" "86400"
}
snippet "static_caching" {
// Aggressive caching for static assets
file_cache_control "public, max-age=31536000, immutable"
compressed
etag
}
snippet "admin_protection" {
// Admin area protection
status 401 realm="Admin Area" users="admin,superuser"
users "admin" "$2b$10$hashedpassword12345"
users "superuser" "$2b$10$anotherhashpassword67890"
limit rate=10 burst=20
}
snippet "mobile_condition" {
condition "is_mobile" {
is_regex "{header:User-Agent}" "(Mobile|Android|iPhone|iPad)" case_insensitive=#true
}
}
snippet "admin_ip_condition" {
condition "is_admin_ip" {
is_remote_ip "192.168.1.10" "10.0.0.5"
}
}
snippet "api_request_condition" {
condition "is_api_request" {
is_regex "{path}" "^/api/"
}
}
snippet "static_asset_condition" {
condition "is_static_asset" {
is_regex "{path}" "\\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot|ico)(?:$|[?#])" case_insensitive=#true
}
}
snippet "development_condition" {
condition "is_development" {
is_equal "{header:X-Environment}" "development"
}
}
// Main website with conditional configuration
example.com {
// TLS configuration
tls "/etc/ssl/certs/example.com.crt" "/etc/ssl/private/example.com.key"
auto_tls_contact "admin@example.com"
// Basic settings
root "/var/www/example.com"
server_administrator_email "admin@example.com"
// Use security headers snippet
use "security_headers"
// Import condition snippets
use "mobile_condition"
use "admin_ip_condition"
use "api_request_condition"
use "static_asset_condition"
use "development_condition"
// Additional security header
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
// Conditional configuration based on mobile detection
if "is_mobile" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
// Mobile-specific settings
header "X-Mobile-Detected" "true"
root "/var/www/example.com/mobile"
// Lighter rate limiting for mobile
limit rate=50 burst=100
}
if_not "is_mobile" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
// Desktop settings
header "X-Mobile-Detected" "false"
limit rate=100 burst=200
}
// Admin IP gets special treatment
if "is_admin_ip" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
// No rate limiting for admin IPs
limit #false
// Additional debug headers
header "X-Admin-Access" "true"
header "X-Client-IP" "{client_ip}"
// Enhanced logging for admin access
log "/var/log/ferron/admin-access.log"
}
// Development environment conditional settings
if "is_development" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
// Development-specific headers
header "X-Environment" "development"
header "X-Debug-Mode" "enabled"
// Disable caching in development
header "Cache-Control" "no-cache, no-store, must-revalidate"
header "Pragma" "no-cache"
header "Expires" "0"
}
if_not "is_development" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
// Production caching
cache
cache_vary "Accept-Encoding" "Accept-Language"
file_cache_control "public, max-age=3600"
}
// URL rewriting
rewrite "^/old-section/(.*)" "/new-section/$1" last=#true
// Error pages
error_page 404 "/var/www/errors/404.html"
error_page 500 "/var/www/errors/500.html"
// Static assets location with conditional caching
location "/assets" remove_base=#true {
root "/var/www/assets"
if "is_static_asset" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
use "static_caching"
}
// CORS for web fonts
if_not "is_static_asset" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
header "Access-Control-Allow-Origin" "*"
}
}
// API endpoints
location "/api" {
if "is_api_request"
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
use "cors_headers"
// API-specific rate limiting
limit rate=1000 burst=2000
// Proxy to API backend
proxy "http://api-backend:8080"
proxy_request_header_replace "X-Real-IP" "{client_ip}"
proxy_request_header "X-Forwarded-Proto" "{scheme}"
}
}
// Admin area with conditional access
location "/admin" {
if "is_admin_ip" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
// Admin IPs get direct access
root "/var/www/admin"
// Special admin headers
header "X-Admin-Direct-Access" "true"
}
if_not "is_admin_ip" {
// Add headers that aren't inherited
use "security_headers"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
// Non-admin IPs require authentication
use "admin_protection"
}
}
// PHP application
fcgi_php "tcp://localhost:9000/"
}
// API subdomain with extensive conditional logic
api.example.com {
// TLS configuration
tls "/etc/ssl/certs/api.example.com.crt" "/etc/ssl/private/api.example.com.key"
// Use CORS headers snippet
use "cors_headers"
// Import reusable condition
use "admin_ip_condition"
// Conditional backend selection based on request path
condition "is_v1_api" {
is_regex "{path}" "^/v1/"
}
condition "is_v2_api" {
is_regex "{path}" "^/v2/"
}
condition "is_auth_request" {
is_regex "{path}" "^/(login|register|refresh|logout)"
}
// Version-specific backend routing
if "is_v1_api" {
// Use CORS headers snippet (again, since it's not inherited)
use "cors_headers"
proxy "http://api-v1-backend1:8080"
proxy "http://api-v1-backend2:8080"
// V1 specific headers
header "X-API-Version" "1.0"
// More restrictive rate limiting for legacy API
limit rate=500 burst=1000
}
if "is_v2_api" {
// Use CORS headers snippet (again, since it's not inherited)
use "cors_headers"
proxy "http://api-v2-backend1:8080"
proxy "http://api-v2-backend2:8080"
proxy "http://api-v2-backend3:8080"
// V2 specific headers
header "X-API-Version" "2.0"
// Higher rate limits for new API
limit rate=2000 burst=4000
}
// Authentication endpoints get special treatment
if "is_auth_request" {
// Use CORS headers snippet (again, since it's not inherited)
use "cors_headers"
// Route to dedicated auth service
proxy "http://auth-service:9090"
// Stricter rate limiting for auth endpoints
limit rate=100 burst=200
// Additional security headers
header "X-Auth-Endpoint" "true"
header "Strict-Transport-Security" "max-age=31536000; includeSubDomains; preload"
}
// Admin IP gets enhanced access
if "is_admin_ip" {
// Use CORS headers snippet (again, since it's not inherited)
use "cors_headers"
// No rate limiting for admin IPs
limit #false
// Admin-specific headers
header "X-Admin-API-Access" "true"
// Enhanced logging
log "/var/log/ferron/api-admin.log"
}
// Health checking
lb_health_check
lb_health_check_max_fails 3
// Proxy settings
proxy_keepalive
proxy_request_header_replace "X-Real-IP" "{client_ip}"
proxy_request_header "X-Forwarded-Proto" "{scheme}"
// Health check endpoint
status 200 url="/health" body="OK"
// Version info endpoint
status 200 url="/version" body="{\"api_versions\":[\"v1\",\"v2\"],\"server\":\"Ferron\"}"
}
// Development subdomain with environment-based configuration
dev.example.com {
// Basic TLS
auto_tls
auto_tls_contact "dev@example.com"
// Use security headers but with relaxed settings
use "security_headers"
// Import reusable condition
use "admin_ip_condition"
// Override some security headers for development
header "X-Frame-Options" "SAMEORIGIN"
// Enhanced logging
log "/var/log/ferron/dev.access.log"
error_log "/var/log/ferron/dev.error.log"
// Development-specific conditions
condition "is_hot_reload" {
is_regex "{path}" "^/(sockjs-node|__webpack_hmr)"
}
condition "is_source_map" {
is_regex "{path}" "\\.map(?:$|[?#])"
}
// Hot reload support
if "is_hot_reload" {
// Non-inherited headers, again
use "security_headers"
header "X-Frame-Options" "SAMEORIGIN"
proxy "http://dev-hmr:3001"
// WebSocket support
proxy_request_header "Connection" "Upgrade"
proxy_request_header "Upgrade" "websocket"
// No caching for hot reload
header "Cache-Control" "no-cache"
}
// Source maps handling
if "is_source_map" {
// Only allow source maps for admin IPs
if "is_admin_ip" {
root "/var/www/dev/sourcemaps"
}
if_not "is_admin_ip" {
status 404 body="Not found"
}
}
// Test endpoints with conditional responses
if "is_admin_ip" {
status 200 url="/test" body="Development server is working (Admin Access)"
status 200 url="/debug" body="{\"client_ip\":\"{client_ip}\",\"method\":\"{method}\",\"path\":\"{path}\"}"
}
if_not "is_admin_ip" {
status 200 url="/test" body="Development server is working"
}
// Default proxy to development backend
proxy "http://dev-backend:3000"
proxy_request_header "X-Dev-Mode" "true"
proxy_request_header "X-Environment" "development"
// Relaxed rate limiting
limit rate=1000 burst=5000
}
// Static content CDN with intelligent caching
cdn.example.com {
// TLS configuration
tls "/etc/ssl/certs/cdn.example.com.crt" "/etc/ssl/private/cdn.example.com.key"
// Static file serving
root "/var/www/cdn"
directory_listing #false
// Use static caching snippet
use "static_caching"
// Import reusable condition
use "admin_ip_condition"
// Conditional caching based on file type
condition "is_image" {
is_regex "{path}" "\\.(png|jpg|jpeg|gif|webp|svg)(?:$|[?#])" case_insensitive=#true
}
condition "is_font" {
is_regex "{path}" "\\.(woff|woff2|ttf|eot|otf)(?:$|[?#])" case_insensitive=#true
}
condition "is_media" {
is_regex "{path}" "\\.(mp4|webm|ogg|mp3|wav)(?:$|[?#])" case_insensitive=#true
}
// Image-specific settings
if "is_image" {
// Extra long caching for images
file_cache_control "public, max-age=2592000, immutable"
// Image-specific headers
header "X-Content-Type" "image"
}
// Font-specific settings
if "is_font" {
// CORS for web fonts
header "Access-Control-Allow-Origin" "*"
header "Access-Control-Allow-Methods" "GET, HEAD, OPTIONS"
// Font-specific caching
file_cache_control "public, max-age=31536000, immutable"
}
// Media-specific settings
if "is_media" {
// Partial content support for media
header "Accept-Ranges" "bytes"
// Media-specific caching
file_cache_control "public, max-age=604800"
}
// Admin IP gets special access
if "is_admin_ip" {
// Allow directory listing for admin IPs
directory_listing #true
// Admin headers
header "X-Admin-CDN-Access" "true"
}
// No rate limiting for CDN
limit #false
// Geographic optimization (example condition)
condition "is_european_request" {
is_regex "{header:CF-IPCountry}" "^(DE|FR|GB|IT|ES|NL|BE|CH|AT|SE|NO|DK|FI|PL)$"
}
if "is_european_request" {
header "X-CDN-Region" "Europe"
// Could proxy to European CDN nodes here
}
if_not "is_european_request" {
header "X-CDN-Region" "Global"
}
}