Documentation
Domain configuration and HTTPS
A full walkthrough of virtual hosts, routing, and automatic HTTPS via Let's Encrypt — no control panel required.
FlashLin has no admin dashboard, no separate certbot process, and no central config that grows unbounded as you add sites. Every domain is one plain TOML file, and certificates renew themselves. This page walks through the whole thing: adding a domain, routing it, and turning on Let's Encrypt.
How virtual hosts work
Point domains_dir at a folder in your main server.toml:
[server]
host = "0.0.0.0"
port = 80
root = "/var/www/public"
domains_dir = "/etc/flashlin/domains"
Every *.toml file FlashLin finds there becomes one virtual host. There's
no registration step and no reload needed to pick up the directory itself
— add, edit, or remove a file and flashlin --watch picks up the change
within two seconds (see Hot reload
below). A request's Host header is matched against each file's
domain.name and domain.aliases; anything that matches no virtual host
falls through to the global [[routes]] in server.toml.
Adding a domain
Create one file per site:
# /etc/flashlin/domains/example.com.toml
[domain]
name = "example.com"
aliases = ["www.example.com"]
root = "/var/www/example.com/public"
index = ["index.html", "index.php"]
server_name = "Example Site" # shown in error pages; defaults to `name`
[[routes]]
path = "/"
root = "/var/www/example.com/public"
index = "index.html"
That's a complete, working virtual host. aliases covers alternate
hostnames that should serve the same site (www. variants, old domains
you're redirecting traffic from) without duplicating the whole block.
Multiple domains
Nothing links these files together — each is independent, so a second site is just a second file:
# /etc/flashlin/domains/blog.example.com.toml
[domain]
name = "blog.example.com"
root = "/var/www/blog/public"
[[routes]]
path = "/"
root = "/var/www/blog/public"
Reverse-proxying a domain to an app backend
A domain's routes work exactly like the global ones — including
proxy_pass, so a domain can front an application server instead of
static files:
[domain]
name = "app.example.com"
root = "/var/www/app.example.com/public" # still needed for e.g. error pages
[[routes]]
path = "/"
proxy_pass = "http://127.0.0.1:3000"
[[routes]]
path = "/assets"
root = "/var/www/app.example.com/public/assets"
See QUERY and WebSockets for proxying WebSocket upgrades and the HTTP QUERY method to that backend.
Per-domain overrides
PHP, security limits, and compression all inherit from the global
server.toml unless a domain overrides them:
[php]
enabled = true
default_handler = "php82"
[[php.handlers]]
name = "php82"
mode = "fpm"
socket = "/run/php/php8.2-fpm.sock"
document_root = "/var/www/example.com/public"
timeout = "30s"
[security]
max_body_size = "5MB"
rate_limit = { limit = "200/min", burst = 20 }
[compression]
gzip = true
brotli = true
Automatic HTTPS with Let's Encrypt
This needs a binary built with the acme feature —
cargo build --release --locked --features acme (already included in
every production package). Enable it in
server.toml, not per-domain:
[tls]
enabled = true
http_port = 80
redirect_http_to_https = true
[tls.acme]
domains = ["example.com", "www.example.com"]
email = "admin@example.com"
cache_dir = "/etc/flashlin/acme"
production = false # staging first — see below
accept_terms = false # read the CA agreement before setting true
One [tls.acme] block issues one certificate covering every domain in
its domains list (up to 100 names) — it's not automatic per-virtual-host
certificate management. If you're serving several unrelated domains that
each need their own certificate, either list them all here (they'll share
one certificate) or use manual certificates per domain instead (below).
Before this will work:
- Every domain in
domainsmust have a public DNS record pointing at this server, including an AAAA record if you have IPv6. - Public TCP port 443 must reach FlashLin's listener for TLS-ALPN-01 validation. A CDN or TLS-terminating proxy in front of FlashLin will intercept the handshake and break validation.
- Port 80 is only used for the HTTP→HTTPS redirect, not the ACME challenge itself.
- Wildcard certificates and DNS-01 validation aren't supported — every name needs to resolve and be reachable directly.
Go through staging first. production = false gets you a
Let's-Encrypt-issued certificate that isn't browser-trusted, specifically
so you can confirm DNS, ports, and your domain list are all correct
without touching Let's Encrypt's real-certificate rate limits. Once a
staging run succeeds — check the logs for certificate deployment — flip
production = true and restart.
$ flashlin --watch /etc/flashlin/server.toml
=> example.com: certificate issued (Let's Encrypt, staging)
=> FlashLin listening on 0.0.0.0:443 (TLS)
cache_dir holds the ACME account key and issued certificates — treat it
like any other private key material. It must live outside every served
document root (FlashLin's config validation rejects a cache directory
inside a public root), persist across restarts, and stay unreadable to
other users: Unix creates it with mode 0700 and refuses to reuse an
existing directory that's group- or world-accessible.
Manual certificates per domain
If you already have certificates from another CA, or need independent certificates for domains that shouldn't share one ACME registration, set TLS directly on the virtual host instead:
[domain]
name = "example.com"
root = "/var/www/example.com/public"
[tls]
enabled = true
cert = "/etc/flashlinssl/certs/example.com.crt"
key = "/etc/flashlinssl/certs/example.com.key"
[[routes]]
path = "/"
root = "/var/www/example.com/public"
Manual certificates and ACME are mutually exclusive per configuration — choose one approach for a given domain, not both.
Validating before you deploy
$ flashlin --check /etc/flashlin/server.toml
Configuration OK: /etc/flashlin/server.toml
--check parses and validates everything — including every file in
domains_dir — without binding a port or contacting Let's Encrypt. It
catches duplicate hostnames, missing domain directories, and malformed
certificate/key pairs before they'd otherwise fail silently on reload.
Applying changes without downtime
$ flashlin --watch /etc/flashlin/server.toml
--watch polls for changes every two seconds (Windows and Unix both) and
hot-swaps the entire configuration — main server.toml, every file in
domains_dir (additions and removals included), routes, security rules,
and manual certificates — without dropping a connection. In-flight
requests finish under the settings they started with; the next new
request picks up whatever generation is current.
An invalid change (bad TOML, a duplicate host, a missing cert file) is rejected and logged — the previous working configuration keeps running untouched. Listener address/port, worker count, and ACME account settings are the exceptions: those require a full restart, and a reload attempt that changes them is rejected outright rather than partially applied.
For a container, set FLASHLIN_WATCH=1 on the entrypoint instead of
passing --watch directly.
Next steps
- Automatic HTTPS and hot reload — the full technical reference: exact reload semantics, what does and doesn't require a restart, and the caveats around cache/state during a reload.
- QUERY and WebSockets — reverse-proxying a domain to an application backend in more depth.
- Installing by distribution — building or
installing a binary with the
acmefeature enabled.
