Server reference
This page describes what a self-hosted Netzilo Server is made of: where it lives on disk, which containers run, which files and volumes hold its data, which ports it uses and how large the host should be. Use it when you operate, secure or troubleshoot a server.
Every command on this page runs on the server, as root or with sudo, from the compose
folder described below.
Find the compose folder
Netzilo Server is a Docker Compose stack. Where it lives depends on how it was installed:
| Installed with | Compose folder | State files |
|---|---|---|
| The one-line installer (Install on your own server) | /opt/netzilo | /opt/netzilo/CREDENTIALS, /opt/netzilo/netzilo-state.env |
| The AWS or Azure Marketplace image | /opt/netzilo/run | /opt/netzilo/CREDENTIALS, /opt/netzilo/netzilo-state.env, /opt/netzilo/metering.env |
Every docker compose command must run from the compose folder. To find it:
if [ -f /opt/netzilo/run/docker-compose.yml ]; then C=/opt/netzilo/run; else C=/opt/netzilo; fi
cd "$C" && sudo docker compose ps
On a marketplace image, cd /opt/netzilo && docker compose ps fails with "no configuration
file provided", because the compose file is one folder deeper.
Containers
| Container | Image | Listens on | Role |
|---|---|---|---|
caddy | caddy | host ports 80, 443 | HTTPS front door. Routes the dashboard, the API, management and signal (gRPC) and the identity provider on one domain |
dashboard | ghcr.io/netzilo/net-dashboard | internal | Admin interface and Workplace |
management | ghcr.io/netzilo/net-management | internal | Control plane: REST API and gRPC |
signal | ghcr.io/netzilo/net-signal | internal | Passes connection offers between peers |
zitadel | ghcr.io/netzilo/zitadel-build | internal | Identity provider (OIDC) |
coturn | coturn/coturn | host network: 3478, 5349, relay range 49152-65535/udp | STUN and TURN relay |
postgres | postgres:16 | internal | Databases netzilo (management) and zitadel |
redis | redis | host port 6379 | Cache for management and the identity provider |
gateway | ghcr.io/netzilo/net-gateway | host port 8443 | Clientless access for browsers with the Netzilo extension, see Clientless-access gateway |
reverse-proxy | ghcr.io/netzilo/net-reverse-proxy | internal, behind Caddy on 443 | Published applications, see Published applications |
support-worker | ghcr.io/netzilo/net-support-worker | internal, no inbound access | The AI Assistant's agent, see AI Assistant |
A current server runs ten containers, eleven with reverse-proxy. A server installed over
plain HTTP has no gateway. Servers installed before the gateway or the AI Assistant
existed run fewer; Upgrade and add components adds them in place.
docker compose ps reports health only for postgres, redis and support-worker; the
others show Up. After a host reboot the stack starts again by itself.
Files in the compose folder
The compose folder holds secrets. It is owned by root; keep it that way.
| File | Holds |
|---|---|
docker-compose.yml | The services, their images, volumes and options. Also the database admin password and the AI Assistant's shared token |
Caddyfile | The front door's sites, routes and TLS settings |
management.json | Management configuration, including the data encryption key, the identity provider's client secret and the relay password |
zitadel.env | Identity provider configuration, including its master key and database passwords |
dashboard.env | Dashboard configuration: sign-in settings and the download mirror |
turnserver.conf | Relay configuration, including the relay credential |
.env | The admin username and initial password |
certs/ | Only with your own or a self-signed certificate: fullchain.pem and privkey.pem |
apps-certs/ | Only with a separate certificate for the applications domain |
Two secrets cannot be recovered if they are lost: the identity provider's master key
(ZITADEL_MASTERKEY in zitadel.env) and the data encryption key
(DataStoreEncryptionKey in management.json). Without the master key the identity
database cannot be read; without the data encryption key setup keys and tokens cannot
be read. Both are part of every backup in Backup and restore.
Do not edit the domain in management.json, zitadel.env, dashboard.env or the
Caddyfile. The domain is also stored in the identity provider's database, so a server
cannot be moved to another domain by editing files.
Volumes
All data lives in Docker named volumes and in the files above. Recreating or upgrading a container does not touch them.
| Volume | Holds |
|---|---|
netzilo_db_data | The PostgreSQL databases (bundled database only) |
netzilo_management | Management's data folder (/var/lib/netzilo in the container), including the geolocation databases |
netzilo_zitadel_certs | The identity provider's certificates |
netzilo_redis_data | The cache. Management rebuilds it from the database at every start |
netzilo_caddy_data | Caddy's Let's Encrypt certificates, which the gateway reads |
netzilo_gateway | The gateway's session counter |
netzilo_reverse_proxy | The published-application tunnels' identities |
Docker Compose adds the project name in front: netzilo_ on a one-line install, run_ on
a marketplace image, for example netzilo_netzilo_management. List the real names with:
sudo docker volume ls | grep netzilo_
docker compose down --volumes, docker volume rm and docker volume prune delete this
data. Never run them on a server you want to keep.
Ports
Inbound to the server
| Port | Protocol | Needed | Purpose |
|---|---|---|---|
443 | TCP | Required, public | Dashboard, API, management and signal, identity provider |
80 | TCP | Required with Let's Encrypt, public | Certificate validation and the redirect to HTTPS |
3478 | UDP | Required, public | STUN and TURN, the addresses the server gives every client |
49152-65535 | UDP | Required for relayed connections, public | Relay allocations. Some stateful firewalls pass this traffic without a rule, but peers behind symmetric NAT and pairs that are both relayed fail without it |
5349 | TCP | Required for clients on networks that block UDP | TURN over TLS. Works only once the relay has a certificate, see Certificates |
3478 TCP, 5349 UDP | Optional | The relay listens, but no client is sent there. Closing them changes nothing | |
8443 | TCP | Required for clientless access, from your users' networks | The gateway |
22 | TCP | Your admin addresses only | SSH |
6379 | TCP | Must not be public | Redis |
The relay runs in host network mode, so a host firewall (ufw, firewalld) filters its
ports. Ports that Docker publishes (80, 443, 8443, 6379) are not filtered by ufw:
block them at the network firewall, or bind Redis to 127.0.0.1 as described in
Install on your own server. The AWS and Azure
templates open the public ports above and keep 6379 closed.
Outbound from the server
443toghcr.ioand Docker Hub (images),pkg.netzilo.com(installer) and Let's Encrypt.- For the AI Assistant: the AI provider endpoint the owner connects, and optionally
github.comandcodeload.github.comto refresh its runbooks. - On a marketplace image: the AWS or Azure marketplace metering endpoint.
Devices
Netzilo clients need no inbound ports. They need outbound 443/tcp to the server's
domain, outbound 3478/udp to it, and outbound UDP for direct connections. Without UDP
they can still relay over 5349/tcp once the relay has a certificate.
Certificates
Find the certificate mode:
grep -E "auto_https off|^\s*tls " "$C/Caddyfile"
No output means Let's Encrypt. auto_https off means your own or a self-signed certificate.
Let's Encrypt. Caddy renews the certificate by itself. It needs the domain's A record
on this server, inbound ports 80 and 443, and outbound 443. The gateway picks up each
renewal within 30 seconds. Let's Encrypt allows 5 duplicate certificates per domain per
week. On a default install Caddy also tries a certificate for *.<your domain>, which it
cannot get this way: the repeated failures for the wildcard name in Caddy's log are
expected and harmless.
Your own certificate. Renewing it is your job. Check its expiry with:
D=go.example.com
echo | openssl s_client -connect $D:443 -servername $D 2>/dev/null | openssl x509 -noout -enddate
To replace it, overwrite certs/fullchain.pem (certificate plus intermediates) and
certs/privkey.pem with the same file names, check that they belong together, and reload:
cd "$C"
sudo openssl pkey -in certs/privkey.pem -pubout -outform DER | md5sum
sudo openssl x509 -in certs/fullchain.pem -pubkey -noout | openssl pkey -pubin -outform DER | md5sum
sudo docker exec caddy caddy reload --config /etc/caddy/Caddyfile
sudo docker compose restart coturn
The two checksums must be identical. The gateway reads the same files and picks up the new pair within 30 seconds.
TURN over TLS. With Let's Encrypt, the relay has no certificate, so 5349/tcp does
not complete TLS. Clients fall back to 3478/udp, so this matters only for sites that block
UDP. With your own certificate, the relay uses the same certs/ files.
Logs
cd "$C"
sudo docker compose logs --tail=200 management
sudo docker compose logs --tail=200 zitadel
sudo docker compose logs --tail=100 caddy
sudo docker compose logs --tail=100 signal coturn dashboard
sudo docker compose logs --tail=200 gateway
sudo docker compose logs -f
On a marketplace image, first-boot setup logs to /var/log/netzilo-firstboot.log.
Container logs are not rotated by default. Limit them as described in
Install on your own server, or check their size
from time to time with sudo du -sh /var/lib/docker/containers/*/.
Management logs at info level. To see rejected API calls and token validation reasons,
change "--log-level", "info" to "debug" in the management service's command: in
docker-compose.yml and run sudo docker compose up -d --force-recreate --no-deps management. Debug output can contain tokens, e-mail addresses and connection details:
turn it back to info as soon as you have what you need.
Change the configuration
Edit the file in the compose folder, then recreate only the service it belongs to:
cd "$C" && sudo docker compose up -d --force-recreate <service>
| Change | Edit | Recreate |
|---|---|---|
| Download mirror shown in the dashboard | dashboard.env: NETZILO_PKG_BASE_URL=https://mirror.example.com | dashboard |
| Relay port range | turnserver.conf: min-port and max-port. Open the same UDP range in the firewall | coturn |
| Relay behind 1:1 NAT | turnserver.conf: add external-ip=<public IP>/<private IP> | coturn |
| Identity provider log level | zitadel.env: ZITADEL_LOG_LEVEL=info | zitadel |
| Bind Redis to the host only | docker-compose.yml: redis port "127.0.0.1:6379:6379" | redis |
Deleting a user in the dashboard also deletes that user in the bundled identity provider.
Sizing
| Minimum | Recommended | |
|---|---|---|
| CPU and memory | 2 vCPU, 4 GB | 2 vCPU, 8 GB (AWS t3.large, Azure Standard_D2s_v5) |
| Disk | 40 GB | 40 GB SSD, and watch /var/lib/docker |
| Devices | up to a few hundred on the base size | 4 vCPU, 16 GB (AWS t3.xlarge, Azure Standard_D4s_v5) beyond that |
The database and container logs are what grow. To see disk use:
df -h / && sudo du -sh /var/lib/docker/volumes/*netzilo_* /var/lib/docker/containers
Architecture questions
- High availability. There is none for the server. Every installation is one host with one database, one identity provider and one relay. Resilience comes from backup and restore to a new host. High availability for routing peers is a separate feature and is supported.
- Kubernetes. The server is delivered as the one-line installer and the AWS and Azure images. There is no published Kubernetes chart for the server. Netzilo clients can run in Kubernetes.
- Relay bandwidth. The relay has no bandwidth limits per session or per user. Size the host for the relayed traffic you expect.
- Client versions. Clients do not have to be upgraded together with the server. Only a minimum-version posture check you configure blocks older clients.
- Pinned images on marketplace images. Every image on the AWS and Azure images,
including the database, cache, proxy and relay, is pinned to a digest. That is expected.
A
docker compose pulldoes not upgrade them, see Upgrade and add components. - Plan. A self-hosted server runs with every feature and no user or device limits. Marketplace images switch the account to Enterprise after the first sign-in.
Related Documentation
- Install on your own server - install the server with one command
- Upgrade and add components - update the containers safely
- Backup and restore - what to back up and how to restore it
- Troubleshooting - symptoms, causes and fixes

