What you get underneath
Each FastNode instance is a VMware virtual machine with the vCPU and RAM allocation of the plan you choose, NVMe-backed storage, a 1 Gbps port and one IPv4 address. You have root. There is no platform layer deciding which runtime versions you may use or how long a request can take, so long-polling, WebSockets, server-sent events and gRPC streams all work without workarounds.
For a UK audience the geography matters. Requests from Manchester, Bristol or Edinburgh reach a London server in a handful of milliseconds. Shaving a transatlantic round trip off every call is worth more than most code-level optimisation, especially for chatty clients that make several requests per screen.
A minimal production layout
Keep the shape simple and every part replaceable:
- Reverse proxy on ports 80 and 443 terminating TLS. Caddy issues certificates automatically; Nginx with certbot works just as well.
- App processes bound to
127.0.0.1on high ports, supervised by systemd. - State in Postgres or Redis, also bound to localhost or a firewall-restricted interface.
- Firewall rules in the portal allowing 22 from your addresses and 80/443 from anywhere. Nothing else.
Supervise with systemd
You do not need a process manager on top of the init system you already have. A unit file restarts crashed processes, captures logs to the journal and starts the service on boot.
[Unit]
Description=orders API
After=network-online.target
Wants=network-online.target
[Service]
User=api
WorkingDirectory=/srv/orders
EnvironmentFile=/etc/orders.env
ExecStart=/srv/orders/bin/server --listen 127.0.0.1:8080
Restart=on-failure
RestartSec=2
LimitNOFILE=65535
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/srv/orders/data
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now orders
journalctl -u orders -f
Proxy and TLS with Caddy
Point your DNS A record at the VPS, then:
api.example.co.uk {
encode zstd gzip
reverse_proxy 127.0.0.1:8080 {
health_uri /healthz
health_interval 10s
}
header {
Strict-Transport-Security "max-age=31536000"
-Server
}
}
Reload with sudo systemctl reload caddy. Certificates renew on their own. If you prefer Nginx, set proxy_http_version 1.1 and pass the Upgrade and Connection headers for WebSocket routes.
Rate limiting at the edge
Abusive clients should be stopped before they reach your application code. In Nginx:
limit_req_zone $binary_remote_addr zone=perip:10m rate=20r/s;
server {
location /v1/ {
limit_req zone=perip burst=40 nodelay;
proxy_pass http://127.0.0.1:8080;
}
}
Apply a stricter zone to login and token endpoints. Return 429 with a Retry-After header so well-behaved clients back off.
Size by measured load
| Shape of the service | Start on |
|---|---|
| Single app process, hosted database elsewhere, low traffic | Spark |
| App plus Redis and proxy, a few hundred requests per second | Boost |
| App and Postgres on one host | Turbo or Nitro |
| Several workers, background queues, heavy JSON serialisation | Warp |
Load-test before launch with wrk or k6 from a separate machine, watching memory as well as CPU. Interpreted runtimes often hit RAM limits before cores are saturated.
Deploy without dropping requests
Run two instances of the app on different ports and list both as upstreams. Deploy by restarting one, waiting for its health check to pass, then restarting the other. The proxy drains each in turn and clients never see a refused connection. Snapshot the VM before schema migrations so you have a fast way back if a migration goes wrong.
Wire it into your tooling
Scoped API keys from the portal let scripts and dashboards read account state, and webhooks push events to an endpoint you run. Keys are displayed once on creation, so drop them straight into your secret store. See the API page for what is available today.