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 withCompose folderState 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

ContainerImageListens onRole
caddycaddyhost ports 80, 443HTTPS front door. Routes the dashboard, the API, management and signal (gRPC) and the identity provider on one domain
dashboardghcr.io/netzilo/net-dashboardinternalAdmin interface and Workplace
managementghcr.io/netzilo/net-managementinternalControl plane: REST API and gRPC
signalghcr.io/netzilo/net-signalinternalPasses connection offers between peers
zitadelghcr.io/netzilo/zitadel-buildinternalIdentity provider (OIDC)
coturncoturn/coturnhost network: 3478, 5349, relay range 49152-65535/udpSTUN and TURN relay
postgrespostgres:16internalDatabases netzilo (management) and zitadel
redisredishost port 6379Cache for management and the identity provider
gatewayghcr.io/netzilo/net-gatewayhost port 8443Clientless access for browsers with the Netzilo extension, see Clientless-access gateway
reverse-proxyghcr.io/netzilo/net-reverse-proxyinternal, behind Caddy on 443Published applications, see Published applications
support-workerghcr.io/netzilo/net-support-workerinternal, no inbound accessThe 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.

FileHolds
docker-compose.ymlThe services, their images, volumes and options. Also the database admin password and the AI Assistant's shared token
CaddyfileThe front door's sites, routes and TLS settings
management.jsonManagement configuration, including the data encryption key, the identity provider's client secret and the relay password
zitadel.envIdentity provider configuration, including its master key and database passwords
dashboard.envDashboard configuration: sign-in settings and the download mirror
turnserver.confRelay configuration, including the relay credential
.envThe 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

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.

VolumeHolds
netzilo_db_dataThe PostgreSQL databases (bundled database only)
netzilo_managementManagement's data folder (/var/lib/netzilo in the container), including the geolocation databases
netzilo_zitadel_certsThe identity provider's certificates
netzilo_redis_dataThe cache. Management rebuilds it from the database at every start
netzilo_caddy_dataCaddy's Let's Encrypt certificates, which the gateway reads
netzilo_gatewayThe gateway's session counter
netzilo_reverse_proxyThe 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

PortProtocolNeededPurpose
443TCPRequired, publicDashboard, API, management and signal, identity provider
80TCPRequired with Let's Encrypt, publicCertificate validation and the redirect to HTTPS
3478UDPRequired, publicSTUN and TURN, the addresses the server gives every client
49152-65535UDPRequired for relayed connections, publicRelay allocations. Some stateful firewalls pass this traffic without a rule, but peers behind symmetric NAT and pairs that are both relayed fail without it
5349TCPRequired for clients on networks that block UDPTURN over TLS. Works only once the relay has a certificate, see Certificates
3478 TCP, 5349 UDPOptionalThe relay listens, but no client is sent there. Closing them changes nothing
8443TCPRequired for clientless access, from your users' networksThe gateway
22TCPYour admin addresses onlySSH
6379TCPMust not be publicRedis

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

  • 443 to ghcr.io and 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.com and codeload.github.com to 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>
ChangeEditRecreate
Download mirror shown in the dashboarddashboard.env: NETZILO_PKG_BASE_URL=https://mirror.example.comdashboard
Relay port rangeturnserver.conf: min-port and max-port. Open the same UDP range in the firewallcoturn
Relay behind 1:1 NATturnserver.conf: add external-ip=<public IP>/<private IP>coturn
Identity provider log levelzitadel.env: ZITADEL_LOG_LEVEL=infozitadel
Bind Redis to the host onlydocker-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

MinimumRecommended
CPU and memory2 vCPU, 4 GB2 vCPU, 8 GB (AWS t3.large, Azure Standard_D2s_v5)
Disk40 GB40 GB SSD, and watch /var/lib/docker
Devicesup to a few hundred on the base size4 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 pull does 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.