Install on your own server

You can run the whole Netzilo Server on one Ubuntu server that you control: a cloud VM, a VPS or bare metal. One command installs the dashboard, the management and signal services, the identity provider, the relay, the database and the gateways, and gets a free HTTPS certificate for your domain. A typical install takes 5 to 10 minutes.

If you deploy on AWS or Azure, the marketplace listings do the same thing from a form. See Hosting on AWS and Hosting on Azure.

How it works

The installer sets up Docker and runs these containers on the server:

ContainerWhat it does
caddyHTTPS front door on ports 80 and 443. Serves the dashboard, the API, management, signal and sign-in on one domain.
dashboardThe admin web interface and the Workplace.
managementThe control plane: accounts, peers, policies, routes, DNS, Edge objects.
signalHelps peers find each other to set up direct connections.
zitadelThe bundled identity provider. Holds users, passwords and MFA, and can federate to your corporate identity provider.
coturnThe relay (STUN and TURN), on its own ports.
postgresThe database for management and the identity provider.
redisA cache for management.
support-workerThe AI Assistant's agent. It has no inbound port.
gatewayThe clientless-access gateway on port 8443, for browsers that have only the Netzilo browser extension.
reverse-proxyPublished applications behind Caddy on port 443. Installed only when you give an applications domain.

A server installed over plain HTTP gets no gateway, because the gateway needs a certificate. See Server reference for images, files, volumes and the full port list.

Before you start

A server

RequirementMinimumRecommended
Operating systemUbuntu 20.04, 22.04 or 24.04, x86_64Ubuntu 22.04 LTS
CPU and memory2 vCPU, 4 GB RAM2 vCPU, 8 GB RAM
Disk40 GB40 GB SSD
Accessroot, or a user with sudo

For more than a few hundred devices, use 4 vCPU and 16 GB RAM. The installer installs Docker if it is missing. On another Linux distribution it warns and continues, untested.

A domain

Pick a domain or subdomain for the server, for example go.example.com, and create its DNS A record pointing at the server's public IP address before you install. The HTTPS certificate is issued only when the domain resolves to the server.

go.example.com   A   203.0.113.10

An applications domain (optional)

To publish private web applications at their own addresses, pick a second domain, for example apps.example.com, and create a wildcard record for it pointing at the same IP:

*.apps.example.com   A   203.0.113.10

It must differ from the server's domain and must not be under netzilo.network. You can also add it later, see Published applications.

Firewall ports

Open these inbound ports in your cloud security group and in any host firewall (ufw, firewalld). The relay runs in host network mode, so a host firewall applies to it.

PortProtocolNeededPurpose
443TCPRequiredDashboard, API, sign-in, and the connection every client keeps to the server
80TCPRequired with Let's EncryptCertificate validation and the redirect to HTTPS
3478UDPRequiredSTUN and TURN: how clients find each other and reach the relay
49152-65535UDPRequiredRelay allocations. Without them, clients that cannot connect directly do not connect at all
5349TCPRecommendedTURN over TLS, the fallback for clients on networks that block UDP. Works only once the relay has a certificate
8443TCPFor clientless accessThe gateway that browsers with the Netzilo extension use
22TCPYour admin address onlySSH

Never expose port 6379 (Redis) to the network.

Outbound access

The server needs outbound HTTPS to pkg.netzilo.com (the installer), ghcr.io (Netzilo images), Docker Hub (Caddy, coturn, PostgreSQL and Redis images) and Let's Encrypt. For a server without internet access, see Air-gapped install.

Step 1: Run the installer

Sign in to the server over SSH and run:

curl -fsSL https://pkg.netzilo.com/download/install-netzilo.sh | sudo bash

The installer asks for these values, in this order:

PromptExampleNotes
Domain (FQDN)go.example.comThe domain you pointed at this server
Public IP203.0.113.10The server's public IP address
Admin first nameAliceRequired
Admin last nameMorganRequired
Admin emailalice@example.comBecomes your login username
Admin passwordAt least 12 characters, with an uppercase letter, a lowercase letter, a number and a symbol. Typing is hidden
Published applications domain (optional)apps.example.comLeave empty to skip
HTTPS certificate11 for Let's Encrypt, 2 for your own certificate, see Step 2

It then shows the values and asks Proceed with install?. If the domain does not resolve to the IP you entered, it warns that Let's Encrypt will fail and asks whether to continue. Fix the DNS record first, or continue: the certificate is requested again automatically once DNS is right. The same check warns when the wildcard record for the applications domain does not point at the server yet.

The install takes 3 to 8 minutes. It ends with Done. Netzilo is starting at https://go.example.com and prints the admin credentials.

Step 2: Choose how HTTPS is secured

Let's Encrypt (default). A trusted certificate is obtained and renewed automatically. It needs the domain's A record pointing at the server and ports 80 and 443 open.

Your own certificate. Choose 2 and give the paths to:

  • the full-chain certificate in PEM format: your certificate followed by every intermediate certificate, and
  • the matching private key in PEM format.

The installer checks that the key matches the certificate, that the certificate has not expired and that the chain is complete, and stops with a clear message if not. A wildcard certificate such as *.example.com is fine. With your own certificate, renewing it is your job, see Server reference.

Step 3: Check the server and sign in

On the server, check that every container is running:

cd /opt/netzilo
sudo docker compose ps

You should see every service Up, and postgres, redis and support-worker reported as healthy. Then, from any machine:

D=go.example.com
curl -sS -o /dev/null -w "dashboard %{http_code}\n" https://$D/
curl -sS https://$D/.well-known/openid-configuration | grep -o '"issuer":"[^"]*"'
curl -sS -o /dev/null -w "api %{http_code}\n" https://$D/api/users

The dashboard answers 200, the issuer is https://go.example.com, and the API answers 401 (it is running and wants a token). Let's Encrypt issues the certificate on the first HTTPS request, so a 502 or a certificate warning in the first minute is normal.

Open https://go.example.com, and sign in with the admin email and password. The credentials are also stored on the server in /opt/netzilo/CREDENTIALS, readable by root only.

If you entered the password yourself, you are not asked to change it at the first sign-in. If someone else typed it for you, change it: open Team > Users, select your user and click Change Password. The unattended install below can also force a change at the first sign-in.

Install without prompts

For automation, put the answers in a file that only root can read, run the installer with --yes, and delete the file afterwards. Do not put the password on the command line: it would end up in your shell history and in the process list. printf '%q' stores any password exactly as typed.

umask 077
IFS= read -r -s -p 'Admin password: ' PW; echo
{
  printf '%s=%q\n' NETZILO_DOMAIN           go.example.com
  printf '%s=%q\n' NETZILO_PUBLIC_IP        203.0.113.10
  printf '%s=%q\n' NETZILO_ADMIN_FIRST_NAME Alice
  printf '%s=%q\n' NETZILO_ADMIN_LAST_NAME  Morgan
  printf '%s=%q\n' NETZILO_ADMIN_EMAIL      alice@example.com
  printf '%s=%q\n' NETZILO_ADMIN_PASSWORD   "$PW"
  printf '%s=%q\n' NETZILO_TLS_MODE         letsencrypt
} | sudo sh -c 'umask 077; cat > /root/netzilo-install.env'
unset PW

curl -fsSL https://pkg.netzilo.com/download/install-netzilo.sh -o install-netzilo.sh
sudo bash -c 'set -a; . /root/netzilo-install.env; set +a; NETZILO_ASSUME_YES=1 bash install-netzilo.sh --yes'
sudo rm -f /root/netzilo-install.env

Add lines to the file, in the same printf '%q' form, for these options:

VariableUse
NETZILO_TLS_MODE=provided with NETZILO_CERT_FILE and NETZILO_KEY_FILEYour own full-chain certificate and key, instead of Let's Encrypt
NETZILO_APPS_DOMAINThe published applications domain, for example apps.example.com
NETZILO_APPS_CERT_FILE and NETZILO_APPS_KEY_FILEA second certificate and key for *.apps.example.com, when your own certificate does not cover it
NETZILO_ADMIN_PASSWORD_CHANGE_REQUIRED=trueForces a password change at the first sign-in. Use it when someone else typed the password
NETZILO_IMAGE_TAGA specific release instead of the latest one

Do these on day one

The installer leaves a few things to you. Do them before you enrol users.

  1. Set up backups. Nothing is backed up automatically. Follow Backup and restore, run the backup once by hand and check that it ends with BACKUP OK.

  2. Configure e-mail (SMTP). Without it, e-mail invitations and password-reset mails never arrive. Until then, add users with Create password. See Identity provider and SSO.

  3. Keep Redis off the network. In /opt/netzilo/docker-compose.yml, change the Redis port line - "6379:6379" to - "127.0.0.1:6379:6379" and run sudo docker compose up -d redis, or block port 6379 at your firewall. Ports that Docker publishes are not filtered by ufw.

  4. Limit container logs. Docker keeps container logs without a size limit. Add this to /etc/docker/daemon.json, then run sudo systemctl restart docker and sudo docker compose up -d --force-recreate in /opt/netzilo (about one minute of downtime):

    { "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "5" } }
    
  5. Turn on what you need. Clientless access and published applications are off until you switch them on under Settings > Permissions. See Clientless-access gateway and Published applications.

  6. Connect an AI provider if you want the AI Assistant. It stays hidden until an owner connects a provider under Integrations > Artificial Intelligence. See AI Assistant.

Self-hosted servers run with every feature and no user or device limits. There is no Plans & Billing tab.

Use an external PostgreSQL database

By default the database runs in the postgres container on the same server. You can use your own PostgreSQL server instead, for example Amazon RDS or Azure Database for PostgreSQL. Decide this at install time: moving a running server to another database later is a backup and restore into the new database.

Requirements:

  • PostgreSQL reachable from the server.
  • An admin role that can create databases and roles (CREATE DATABASE, CREATE ROLE).
  • TLS to the database: sslmode=require is recommended.

The installer creates the databases netzilo and zitadel and the role zitadel. No postgres container is created. Backups of the database become your database provider's job; take provider snapshots as well as the file backup.

On AWS, choose external for Database mode in the CloudFormation form and fill in the DB fields. On your own server, run the installer engine directly (the one-line installer always uses the bundled database). Docker must already be installed:

sudo mkdir -p /opt/netzilo && cd /opt/netzilo
sudo curl -fsSL https://pkg.netzilo.com/download/install_netzilo.sh -o install_netzilo.sh
umask 077
IFS= read -r -s -p 'Admin password: ' PW; echo
IFS= read -r -s -p 'Database admin password: ' DBPW; echo
{
  printf '%s=%q\n' NETZILO_NONINTERACTIVE    1
  printf '%s=%q\n' NETZILO_STATE_DIR         /opt/netzilo
  printf '%s=%q\n' NETBIRD_DOMAIN            go.example.com
  printf '%s=%q\n' NETZILO_TLS_MODE          letsencrypt
  printf '%s=%q\n' NETZILO_ADMIN_EMAIL       alice@example.com
  printf '%s=%q\n' NETZILO_ADMIN_FIRST_NAME  Alice
  printf '%s=%q\n' NETZILO_ADMIN_LAST_NAME   Morgan
  printf '%s=%q\n' NETZILO_ADMIN_PASSWORD    "$PW"
  printf '%s=%q\n' NETZILO_DB_MODE           external
  printf '%s=%q\n' NETZILO_DB_HOST           db.example.com
  printf '%s=%q\n' NETZILO_DB_PORT           5432
  printf '%s=%q\n' NETZILO_DB_ADMIN_USER     netzilo_admin
  printf '%s=%q\n' NETZILO_DB_ADMIN_PASSWORD "$DBPW"
  printf '%s=%q\n' NETZILO_DB_SSLMODE        require
} | sudo sh -c 'umask 077; cat > /root/netzilo-install.env'
unset PW DBPW
sudo bash -c 'set -a; . /root/netzilo-install.env; set +a; bash install_netzilo.sh'
sudo rm -f /root/netzilo-install.env

The engine writes its files into the folder it runs in, so run it from /opt/netzilo. NETZILO_DB_SSLMODE takes require, verify-full or disable.

Air-gapped install

A server with no internet access can install from image archives you bring with you. Let's Encrypt cannot work offline, so use your own certificate (NETZILO_TLS_MODE=provided, with fullchain.pem and privkey.pem in the folder named by NETZILO_CERT_DIR, default ./certs) or NETZILO_TLS_MODE=selfsigned. A self-signed certificate works only on devices and browsers that trust it.

  1. On a connected machine, download the installer engine from https://pkg.netzilo.com/download/install_netzilo.sh. The IMAGE_ lines at its top name every image it uses.

  2. Pull each of those images and save it as an archive, for example:

    docker pull ghcr.io/netzilo/net-gateway:latest
    docker save ghcr.io/netzilo/net-gateway:latest | gzip > netzilo-gateway.tar.gz
    
  3. Copy the archives and the engine to the server. Install Docker there.

  4. Run the engine as in Use an external PostgreSQL database, without the NETZILO_DB_ lines, and add NETZILO_IMAGE_SOURCE=disk and NETZILO_IMAGES_DIR=<folder with the archives>. The engine loads every archive in that folder before it starts.

The AI Assistant needs to reach an AI provider, so on an air-gapped server use a provider endpoint inside your network. See AI Assistant.

Host notes

The relay runs in host network mode, so the host's own firewall rules apply to its ports.

  • Hetzner. Hetzner's firewall is stateless: it does not let replies to the server's own outgoing UDP traffic back in. Besides the ports above, allow inbound UDP on the server's local port range, which this command prints:

    cat /proc/sys/net/ipv4/ip_local_port_range
    
  • Oracle Cloud. Oracle's Linux images ship iptables rules that drop inbound traffic the cloud security rules allow. Open the relay ports on the host as well:

    sudo iptables -I INPUT -p udp --dport 3478 -j ACCEPT
    sudo iptables -I INPUT -p tcp --dport 5349 -j ACCEPT
    sudo iptables -I INPUT -p udp --dport 49152:65535 -j ACCEPT
    

    Make the rules persistent the way your image keeps iptables rules. Oracle discourages ufw on its images.

Uninstall

This deletes the server and all its data: users, devices, policies and the identity provider. Take a backup first if you may need anything from it.

cd /opt/netzilo
sudo docker compose down --volumes
sudo rm -rf /opt/netzilo

Troubleshooting

The installer stops with password must contain upper, lower, number and a symbol. The password does not meet the policy. Use at least 12 characters with an uppercase letter, a lowercase letter, a number and a symbol.

The browser shows a certificate warning. The certificate is not issued yet. Check that the domain's A record points at the server and that ports 80 and 443 are open, then wait a minute and reload. You do not need to run the installer again: the certificate is requested again automatically.

The installer stops with Private key does not match the certificate, Certificate expired or fullchain.pem contains only the leaf certificate. Supply a full chain (your certificate plus the intermediates) and the key that belongs to it.

The installer stops with The domain name cannot be *.netzilo.com. Use a domain you own.

The installer stops with ERROR calling ... or waits forever for Zitadel's PAT. The identity provider could not finish its first start, usually because image downloads or the database took too long, or the server has less than 4 GB of memory. Remove the partial installation (see Uninstall), then install again.

Port 80 or 443 is already in use. Another web server runs on the host. Stop and disable it before you install.

The curl command fails. The server cannot reach pkg.netzilo.com. Download install-netzilo.sh and install_netzilo.sh on another machine, copy both to the server into the same folder, and run sudo bash install-netzilo.sh there.

Sign-in loops back, or shows "Oops, something went wrong". You opened the server by another name, such as its IP address. Always use https:// followed by the exact domain you installed with.

For problems after the install, see Troubleshooting.