Your first sites

This page walks through the three kinds of site most servers run: plain files, an application behind the proxy, and PHP. Each takes one short file — or a few clicks in the panel.

Every example assumes your domain's DNS A record (and AAAA for IPv6) already points at the server, and that ports 80 and 443 are open. Replace example.com with your own name.

The shape of a host file#

A website is one file in /etc/arka/hosts/. The file name (without .toml) is the host's id. The smallest useful host:

# /etc/arka/hosts/example.com.toml
server_names = ["example.com"]

[[routes]]
path = "/"
handler = { type = "static", root = "/var/www/example" }
  • server_names — the names this site answers to. Wildcards (*.example.com) and regular expressions work too.
  • [[routes]] — one block per path. A route sends the requests whose path starts with path to a handler. Like nginx location blocks: exact matches win, then regular expressions, then the longest prefix.
  • Leaving listen out means port 80 on every address; with a [tls] section, also port 443.

Saving the file is enough. Arka watches /etc/arka/hosts/ and applies a changed file in a couple of milliseconds, without a restart. To check a file before it goes live:

sudo -u arka arka --config /etc/arka/arka.toml check

If a file has a mistake, check prints the line and what is wrong; a running Arka keeps serving that site's last good version until you fix it, and the panel shows the error with a Fix in editor button.

Prefer the panel? Press New host (or N) and pick a template — Static site, Single-page app, Reverse proxy, PHP site, File share or Redirect. The wizard asks for the names and the folder or address, shows the file it will write, and makes the site live. Everything below can be done that way; the files are shown so you know what the panel writes.

A static website with HTTPS#

1. Put the files on the server, readable by the arka user:

sudo mkdir -p /var/www/example
sudo cp -r ./my-site/* /var/www/example/
sudo chmod -R a+rX /var/www/example     # world-readable files and folders

2. Tell Arka where to send expiry warnings (once per server) — add to /etc/arka/arka.toml, then sudo systemctl restart arka:

[acme]
email = "you@example.com"

3. Create the host:

# /etc/arka/hosts/example.com.toml
server_names = ["example.com", "www.example.com"]

[tls]
acme = true            # a free Let's Encrypt certificate, renewed automatically
redirect_http = true   # http:// visitors are sent to https://
hsts = true            # browsers remember to use HTTPS

[[routes]]
path = "/"
handler = { type = "static", root = "/var/www/example", cache_control = "public, max-age=300" }

Within a few seconds Arka asks Let's Encrypt for a certificate, answers its check on port 80, and starts serving HTTPS with it. Until the certificate arrives a temporary self-signed one stands in. The Certificates page in the panel shows each certificate, when it expires and when it will be renewed — about 30 days before expiry, with nothing for you to do.

4. Check it:

curl -I https://example.com/
HTTP/2 200
server: arka/0.1.12
content-type: text/html; charset=utf-8
etag: "…"
cache-control: public, max-age=300
strict-transport-security: max-age=31536000; includeSubDomains

What you get without configuring anything: small files are kept in memory and text files are compressed once — brotli, zstd or gzip, depending on what the browser accepts — with the compressed copy kept for every later visitor (pages an application makes per request get zstd first, which is quicker to make); ETag/Last-Modified and range requests for resumable downloads; HTTP/2; and protection against ../ path tricks. Files whose names start with a dot (.env, .git/) are not served unless you allow it (.well-known/ always is).

Testing certificates? Let's Encrypt limits how many certificates you can request per week. While experimenting, set ca = "letsencrypt-staging" under [acme] — browsers do not trust those certificates, but there is no limit — and remove it when everything works.

One name, not two#

Search engines prefer one address per page. To send www.example.com to example.com, take www out of the site's server_names and give it a small host of its own:

# /etc/arka/hosts/www.example.com.toml
server_names = ["www.example.com"]

[tls]
acme = true
redirect_http = true

[[routes]]
path = "/"
handler = { type = "redirect", to = "https://example.com{uri}", code = 301 }

A single-page app (React, Vue, Svelte, Angular)#

Single-page apps answer every path with index.html and let JavaScript pick the page. Add a fallback:

handler = { type = "static", root = "/var/www/app/dist", fallback = "index.html" }

Real files (/assets/app.3f9c.js) are still served as they are; anything else gets index.html with status 200.

An application behind the proxy#

Node.js, Python (Django, Flask, FastAPI), Ruby, Go, Java — anything that listens on an HTTP port. Say the application listens on 127.0.0.1:3000:

# /etc/arka/hosts/app.example.com.toml
server_names = ["app.example.com"]

[tls]
acme = true
redirect_http = true

[[routes]]
path = "/"
handler = { type = "proxy", upstreams = ["127.0.0.1:3000"] }

That is all most applications need. Arka:

  • keeps connections to the application open and reuses them;
  • tells the application who the visitor is with X-Forwarded-For, X-Forwarded-Proto (https), X-Forwarded-Host and X-Real-IP — and replaces any copies a visitor sends, so they cannot be forged;
  • passes WebSocket connections through (Socket.IO, Phoenix, Rails Action Cable…);
  • retries a request on another upstream when a connection fails before the request was sent — and never sends a POST twice;
  • answers 502 Bad Gateway with a clean error page, rather than hanging, when the application is down.

Make the application listen on 127.0.0.1 only, so visitors cannot bypass Arka.

Several application servers? List them all, and Arka balances between them, checks their health and skips the ones that fail:

[routes.handler]
type = "proxy"
upstreams = ["10.0.0.11:3000", "10.0.0.12:3000", "10.0.0.13:3000 backup"]
strategy = "least_conn"
health_check = { path = "/healthz", interval = "5s" }

More in Guides → Load balancing and the proxy reference.

A PHP site#

Arka runs PHP through php-fpm, the same way nginx does.

1. Install PHP:

sudo apt install php-fpm php-mysql php-gd php-curl php-xml php-mbstring php-zip

2. Let Arka talk to php-fpm. php-fpm listens on a socket that only www-data may use. Add the arka user to that group, and restart Arka:

sudo usermod -aG www-data arka
sudo systemctl restart arka

3. Create the host (the socket's name carries the PHP version — check with ls /run/php/):

# /etc/arka/hosts/shop.example.com.toml
server_names = ["shop.example.com"]

[tls]
acme = true
redirect_http = true

[[routes]]
path = "/"
[routes.handler]
type = "fastcgi"
address = "unix:/run/php/php8.3-fpm.sock"
root = "/var/www/shop"
front_controller = "/index.php"   # WordPress, Laravel, Symfony, Drupal…
max_conns = 5                     # php-fpm's pm.max_children (5 on Ubuntu)
queue = { size = 200, timeout = "30s" }

Existing .php files run, other files (images, CSS, JavaScript) are served by Arka directly from memory, and any other path goes to index.php — which is how WordPress and most frameworks expect "pretty" URLs to work. Paths can never escape root, and only files ending in .php are ever executed.

Why max_conns? php-fpm runs a fixed number of worker processes (pm.max_children, five in Ubuntu's default pool) and ties one up for every open connection. With max_conns set to that number, Arka never opens more connections than PHP has workers, keeps them open between requests — about a quarter faster — and lets visitors wait their turn in the queue when every worker is busy. Without max_conns, Arka closes each connection after its request: a little slower, but it can never hold a worker that another visitor needs. Raise both together when you give php-fpm more workers.

4. File permissions. PHP runs as www-data and Arka as arka. A common, safe layout: files owned by your deploy user, readable by everyone, and only the upload folder writable by PHP:

sudo chown -R deploy:www-data /var/www/shop
sudo find /var/www/shop -type d -exec chmod 755 {} +
sudo find /var/www/shop -type f -exec chmod 644 {} +
sudo chmod -R g+w /var/www/shop/wp-content/uploads   # WordPress, for example

Recipes for WordPress and Laravel are in the guides, and every FastCGI setting is in the reference.

What next#

  • Protect the site: turn on the firewall — [waf] with level = "standard", or the switch on the host's page in the panel, which lets it learn for a day before it blocks (it is off until you do) — and add rate limits, bans and sign-in where they fit.
  • Watch it: the panel's Overview and the host's page show traffic, errors and response times live; Logs shows every request.
  • Recipes for common applications are in Guides and recipes.