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.
Your domain is permanent. You cannot move a server to another domain later without a new
installation and re-enrolling every device. The domain cannot be under netzilo.com.
How it works
The installer sets up Docker and runs these containers on the server:
| Container | What it does |
|---|---|
caddy | HTTPS front door on ports 80 and 443. Serves the dashboard, the API, management, signal and sign-in on one domain. |
dashboard | The admin web interface and the Workplace. |
management | The control plane: accounts, peers, policies, routes, DNS, Edge objects. |
signal | Helps peers find each other to set up direct connections. |
zitadel | The bundled identity provider. Holds users, passwords and MFA, and can federate to your corporate identity provider. |
coturn | The relay (STUN and TURN), on its own ports. |
postgres | The database for management and the identity provider. |
redis | A cache for management. |
support-worker | The AI Assistant's agent. It has no inbound port. |
gateway | The clientless-access gateway on port 8443, for browsers that have only the Netzilo browser extension. |
reverse-proxy | Published 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
| Requirement | Minimum | Recommended |
|---|---|---|
| Operating system | Ubuntu 20.04, 22.04 or 24.04, x86_64 | Ubuntu 22.04 LTS |
| CPU and memory | 2 vCPU, 4 GB RAM | 2 vCPU, 8 GB RAM |
| Disk | 40 GB | 40 GB SSD |
| Access | root, 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.
| Port | Protocol | Needed | Purpose |
|---|---|---|---|
443 | TCP | Required | Dashboard, API, sign-in, and the connection every client keeps to the server |
80 | TCP | Required with Let's Encrypt | Certificate validation and the redirect to HTTPS |
3478 | UDP | Required | STUN and TURN: how clients find each other and reach the relay |
49152-65535 | UDP | Required | Relay allocations. Without them, clients that cannot connect directly do not connect at all |
5349 | TCP | Recommended | TURN over TLS, the fallback for clients on networks that block UDP. Works only once the relay has a certificate |
8443 | TCP | For clientless access | The gateway that browsers with the Netzilo extension use |
22 | TCP | Your admin address only | SSH |
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:
| Prompt | Example | Notes |
|---|---|---|
| Domain (FQDN) | go.example.com | The domain you pointed at this server |
| Public IP | 203.0.113.10 | The server's public IP address |
| Admin first name | Alice | Required |
| Admin last name | Morgan | Required |
| Admin email | alice@example.com | Becomes your login username |
| Admin password | At least 12 characters, with an uppercase letter, a lowercase letter, a number and a symbol. Typing is hidden | |
| Published applications domain (optional) | apps.example.com | Leave empty to skip |
| HTTPS certificate | 1 | 1 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:
| Variable | Use |
|---|---|
NETZILO_TLS_MODE=provided with NETZILO_CERT_FILE and NETZILO_KEY_FILE | Your own full-chain certificate and key, instead of Let's Encrypt |
NETZILO_APPS_DOMAIN | The published applications domain, for example apps.example.com |
NETZILO_APPS_CERT_FILE and NETZILO_APPS_KEY_FILE | A second certificate and key for *.apps.example.com, when your own certificate does not cover it |
NETZILO_ADMIN_PASSWORD_CHANGE_REQUIRED=true | Forces a password change at the first sign-in. Use it when someone else typed the password |
NETZILO_IMAGE_TAG | A 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.
-
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. -
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.
-
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 runsudo docker compose up -d redis, or block port 6379 at your firewall. Ports that Docker publishes are not filtered byufw. -
Limit container logs. Docker keeps container logs without a size limit. Add this to
/etc/docker/daemon.json, then runsudo systemctl restart dockerandsudo docker compose up -d --force-recreatein/opt/netzilo(about one minute of downtime):{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "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.
-
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=requireis 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.
-
On a connected machine, download the installer engine from
https://pkg.netzilo.com/download/install_netzilo.sh. TheIMAGE_lines at its top name every image it uses. -
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 -
Copy the archives and the engine to the server. Install Docker there.
-
Run the engine as in Use an external PostgreSQL database, without the
NETZILO_DB_lines, and addNETZILO_IMAGE_SOURCE=diskandNETZILO_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
iptablesrules 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 ACCEPTMake the rules persistent the way your image keeps
iptablesrules. Oracle discouragesufwon 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.
Related Documentation
- Self-hosted vs. Cloud - what you run yourself, and what Netzilo Cloud runs for you
- Server reference - containers, files, volumes and ports
- Upgrade and add components - keep the server current
- Backup and restore - protect the server's data
- Identity provider and SSO - users, SMTP and single sign-on
- Install Netzilo - install the Netzilo client on devices

