Deployment guide

Nginx UI

Videos

Overview: Nginx UI on Ubuntu 24.04 on Azure: overview (watch on YouTube)

Launch and log in: Nginx UI on Ubuntu 24.04 on Azure: launch and log in (watch on YouTube)

Deep Dive: Nginx UI on Ubuntu 24.04 on Azure: deep dive (watch on YouTube)

Overview

Nginx UI is a web interface for nginx. From a browser, you create and edit sites, streams and upstreams. You edit any nginx configuration file with syntax checking, and test, reload and restart nginx. You issue and renew Let's Encrypt certificates, and read nginx's access and error logs. Every change is checked with nginx -t before it is applied.

This SecureAnyCloud image runs Nginx UI on Ubuntu 24.04 LTS on Microsoft Azure, with a unique admin password, HTTPS and a locked-down network surface from the first boot.

What you get:

  • No default login. The image contains no Nginx UI account, no database and none of Nginx UI's secrets. On the first boot of your VM, a one-time service generates an admin password unique to the VM. It completes Nginx UI's first-run setup through its local API with the network shut off, and proves that the password works and that guessable ones are refused. Only then does it open the web interface. Nginx UI's setup page is never reachable from the network, and the password is written only to a file readable by root.
  • HTTPS on port 443, from the first boot. nginx serves Nginx UI on port 443 with a certificate generated on your VM. Port 80 redirects to HTTPS, and a request for a host name the VM does not serve is dropped.
  • Nginx UI on localhost only. Nginx UI listens on 127.0.0.1:9000, behind nginx. Port 9000 is not open to the network, and the image's network rules do not include it. If anyone moves Nginx UI's listener (in Preferences, or by editing its settings file), it is put back on 127.0.0.1:9000 the next time the service starts.
  • The browser terminal is off. Nginx UI includes a terminal that gives you a shell on the VM in your browser. It is disabled on this image. Step 9 explains how to turn it on and what that means.
  • Hardened front door. nginx hands Nginx UI the real client address and the configured host name, never values a client sends. It refuses a request that changes something when it carries another site's Origin (and every cross-origin WebSocket), and refuses Nginx UI's setup routes. It rate-limits the login routes on top of Nginx UI's own lockout, marks every cookie Secure, and keeps WebSocket session tokens out of the access log.
  • Azure metadata blocked. Nginx UI, nginx and the first-boot service cannot reach the Azure Instance Metadata Service or the Azure WireServer. Nginx UI fetches URLs you give it (certificate authorities, DNS provider APIs, S3 backup targets, cluster nodes, upstream checks), so a mistaken or malicious setting can never reach the VM's metadata or a managed identity token. The Azure agent keeps its own access.
  • The current Nginx UI release. Nginx UI 2.9.1 is outside every published Nginx UI security advisory (69 at the time of the build, including the critical ones that affect 2.4.2). SecureAnyCloud compiled it from the unmodified upstream source with one newer Go dependency (details in Step 12).
  • Fully patched. Ubuntu 24.04 LTS is patched at build time, with unattended security upgrades switched on.
  • What is on the image: Nginx UI 2.9.1 (built from the v2.9.1 source with Go 1.27.3), nginx 1.24 (from Ubuntu 24.04), cloud-init and the Azure Linux agent.
  • Defaults: the default user is azureuser, the architecture is x64 (Gen2), and the recommended VM size is Standard_B2s (2 vCPU, 4 GiB).

Prerequisites

  • An Azure subscription, an SSH key pair, and a virtual network and subnet in the target region.
  • A network security group that allows TCP 22 (SSH) from your admin network and TCP 443 (the Nginx UI web interface and your HTTPS sites) from the networks that need it. Also allow TCP 80 for HTTP sites, the redirect to HTTPS and Let's Encrypt HTTP validation.

Never open 9000. Nginx UI listens on the loopback address only.

1

Deploy the VM

In the Azure portal, open Create a resource, search for Nginx UI on Ubuntu 24.04 LTS by SecureAnyCloud and select Create. On the Basics tab, choose Standard_B2s and SSH public key authentication, with azureuser as the user name. On the Networking tab, allow 22, 80 and 443 as above.

To deploy with the Azure CLI instead, replace the values in angle brackets. The image URN is shown on the offer's Usage information + support tab:

az vm create --resource-group <resource-group> --name nginx-ui --image <image-urn-from-the-offer> \
  --size Standard_B2s --admin-username azureuser --ssh-key-values ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard --nsg-rule SSH
az network nsg rule create --resource-group <resource-group> --nsg-name nginx-uiNSG --name web \
  --priority 1010 --access Allow --protocol Tcp --destination-port-ranges 80 443 --source-address-prefixes <your-cidr>

First boot takes under a minute after the VM is running.

2

Connect to your VM

ssh azureuser@<public-ip>
3

Check the services

systemctl is-active nginx-ui-address nginx-ui nginx
sudo test -f /var/lib/sac/data/nginx-ui-firstboot.done && echo FIRSTBOOT_DONE
active active active FIRSTBOOT_DONE

The units do the following:

  • nginx-ui-address reads the VM's IP addresses from the Azure Instance Metadata Service at each boot.
  • nginx-ui-firstboot ran once on the first boot.
  • nginx-ui and nginx are the web interface and the web server it manages.

Only SSH and nginx listen on network ports. Nginx UI listens on the loopback address:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | grep -v '127.0.0.5' | sort
0.0.0.0:22 users:(("sshd"),("systemd")) 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) 127.0.0.1:9000 users:(("nginx-ui")) [::]:22 users:(("sshd"),("systemd")) [::]:443 users:(("nginx"),("nginx"),("nginx")) [::]:80 users:(("nginx"),("nginx"),("nginx"))
4

Get your URL and admin password

The URL and the admin password are in a file that only root can read. Read it:

sudo cat /root/nginx-ui-credentials.txt
# Nginx UI 2.9.1 - generated on first boot by nginx-ui-firstboot.service.
# Every value below is unique to this VM; nothing was baked into the image. Keep this file private.
# Web interface (HTTPS, self-signed certificate unique to this VM): sign in as admin with the password below,
# then change it (top right -> Account). NGINX_UI_URL=https://<public-ip>/ NGINX_UI_ADMIN_USERNAME=admin NGINX_UI_ADMIN_PASSWORD=<NGINX_UI_ADMIN_PASSWORD> NGINX_UI_HOST=<public-ip>
# The certificate served on 443 (/etc/nginx/sac/tls/nginx-ui.crt), to compare with your browser's warning: TLS_CERT_SHA256=<certificate-fingerprint> # Self-test:
sudo nginx-ui-selftest Serve the UI under your own DNS name:
sudo nginx-ui-set-host <dns-name>
# Guide: https://secureanycloud.com/stacks/nginx-ui/
sudo stat -c '%a %U %n' /root/nginx-ui-credentials.txt
sudo grep -c '^NGINX_UI_ADMIN_PASSWORD=' /root/nginx-ui-credentials.txt
600 root /root/nginx-ui-credentials.txt 1

The URL uses the VM's public IP address as the Azure Instance Metadata Service reports it. When the VM has no public IP, it uses the private address. The certificate also names the private IP address.

To serve Nginx UI under your own DNS name, see Step 8.

5

Sign in

Open https://<public-ip>/ in your browser. The certificate is self-signed and generated on your VM. Compare its SHA-256 fingerprint with TLS_CERT_SHA256, accept it, and sign in as admin with NGINX_UI_ADMIN_PASSWORD.

Change the password at once from the account menu at the top right (Account). Then update the credentials file so the self-test keeps working (Step 11).

The dashboard, shown at the top of this guide, reports the server's load and the nginx status.

6

Manage nginx

Everything Nginx UI changes is the system nginx on this VM, exactly as on any Ubuntu server. Your sites live in /etc/nginx/sites-available and /etc/nginx/sites-enabled. Streams live in /etc/nginx/streams-available and /etc/nginx/streams-enabled.

Manage Sites lists your sites. The image ships the stock Ubuntu default site, disabled, as a template. Add a site and edit it in the guided editor or as plain configuration. Then enable it, and Nginx UI writes the file, runs nginx -t and reloads nginx.

This is how the sites fit with the HTTPS front door:

  • The server blocks that serve Nginx UI itself are in /etc/nginx/conf.d/sac-nginx-ui.conf. They are not one of your sites and do not appear in Manage Sites, so saving, enabling or deleting a site never touches them. They answer only for the VM's own names (the public and private IP addresses, or your own name after Step 8). Every other host name on ports 80 and 443 is yours to serve.
  • A site of yours that claims default_server on port 80 or 443 conflicts with them. nginx -t fails, and Nginx UI refuses to enable the site and puts it back the way it was, while the web interface stays up.
  • Manage Configs can edit any file under /etc/nginx, including sac-nginx-ui.conf. Editing or removing that file deliberately can take the web interface offline. If that happens, reach Nginx UI through an SSH tunnel and fix it there:
ssh -L 9000:127.0.0.1:9000 azureuser@<public-ip>

Then open http://127.0.0.1:9000/ on your own computer. The tunnel carries it over SSH.

Nginx UI can also reload and restart nginx, with the buttons on the dashboard. nginx always runs as its own nginx.service, with the same metadata block.

7

Certificates

To issue Let's Encrypt certificates from Nginx UI, first set the email address for your ACME account under Preferences > Certificate. The image ships none, so no ACME account exists until you choose one.

Then open a site, switch on Encrypt with Let's Encrypt and follow the prompts.

  • HTTP validation needs TCP 80 open to the internet in the network security group, and a DNS record for the site pointing at the VM.
  • DNS validation needs your DNS provider's API credentials (Certificates > DNS Credentials).
8

Your own DNS name for the web interface

Point a DNS record at the VM. For example, use an Azure DNS A record, or the public IP's DNS name label <label>.<region>.cloudapp.azure.com. Then tell the image to serve Nginx UI under that name.

The command below issues a new self-signed certificate for the name. It updates nginx, Nginx UI's base URL and passkey settings, and the credentials file:

sudo nginx-ui-set-host nginx-ui.<your-domain>

Running sudo nginx-ui-set-host with no name returns to the VM's own IP address.

To use a certificate from your own CA or from Let's Encrypt for the web interface, replace /etc/nginx/sac/tls/nginx-ui.crt and /etc/nginx/sac/tls/nginx-ui.key, keeping the key mode 0600. Then run sudo systemctl reload nginx.

9

The web terminal

Nginx UI's Terminal page is a shell on this VM in your browser. As shipped upstream, it is a root shell, available to anyone signed in to Nginx UI. It is disabled on this image, and the page prints a notice instead.

Use SSH instead. If you want the terminal, keep in mind that anyone who can sign in to Nginx UI, or steal a signed-in session, then has a shell on the VM.

To turn it on with a login prompt, set StartCmd = login under [terminal] in /etc/nginx-ui/app.ini and restart Nginx UI. The person must then sign in with a Linux account and password on the VM:

sudo sed -i 's#^StartCmd = .*#StartCmd = login#' /etc/nginx-ui/app.ini && sudo systemctl restart nginx-ui

StartCmd = bash gives a root shell with no further prompt. We do not recommend it.

The web interface cannot change this setting. Only the file can. To turn the terminal off again, set it back to /usr/local/sbin/nginx-ui-terminal-disabled.

10

Security checks

nginx-ui-selftest re-checks the appliance with this VM's credentials. It verifies that:

  • The services are running.
  • Nginx UI listens on 127.0.0.1:9000 only.
  • HTTPS works with this VM's certificate.
  • The setup window is closed and refused at nginx.
  • The API refuses anonymous calls.
  • A wrong password and the default password are refused, and the password in the credentials file is accepted.
  • Nginx UI reads the nginx status.

Run it:

sudo nginx-ui-selftest
SELFTEST_OK nginx-ui+nginx active; Nginx UI 127.0.0.1:9000 only; https 200 (Nginx UI 2.9.1); setup closed and refused; API 403 anonymous; wrong/default password refused; per-VM password OK; host <public-ip>

The metadata block is a systemd IPAddressDeny rule on Nginx UI, nginx and the first-boot service. It covers the Azure Instance Metadata Service (169.254.169.254) and the Azure WireServer (168.63.129.16).

nginx-ui-address, which only reads the VM's IP addresses before anything else starts, and the Azure agent keep their access:

for u in nginx-ui nginx nginx-ui-firstboot nginx-ui-address walinuxagent; do printf '%s: ' $u; systemctl show -p IPAddressDeny --value $u.service | tr ' ' '\n' | sort | tr '\n' ' '; echo; done
nginx-ui: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 nginx: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 nginx-ui-firstboot: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 nginx-ui-address: walinuxagent:

Check the front door from the VM itself. The commands send a state-changing request from another site's page, then request the setup routes, the setup status and an anonymous API call:

H=$(sudo sed -n 's/^NGINX_UI_HOST="\(.*\)"$/\1/p' /etc/nginx/sac/nginx-ui-host.env) C=(--cacert /etc/nginx/sac/tls/nginx-ui.crt --resolve "$H:443:127.0.0.1")
sudo curl -s "${C[@]}" -o /dev/null -w '%{http_code}\n' -X POST -H 'Origin: https://evil.example' "https://$H/api/nginx/test"
sudo curl -s "${C[@]}" -w '\n' -X POST "https://$H/api/setup/install"
sudo curl -s "${C[@]}" -w '\n' "https://$H/api/install"
sudo curl -s "${C[@]}" -w '\n' "https://$H/api/user"
403 {"message":"Nginx UI setup is completed locally on this VM (sac)"} {"lock":true,"timeout":false} {"message":"Authorization failed"}

Nginx UI authenticates every API call with the Authorization header, and a WebSocket with a short-lived token in its URL. It never accepts a browser cookie as a login, so a link on another site cannot act with your session.

11

Passwords and access

  • Change the admin password in the web interface (Account, top right). Then put the new password into /root/nginx-ui-credentials.txt (sudo nano /root/nginx-ui-credentials.txt) so nginx-ui-selftest keeps working. Alternatively, delete the file once you have stored the password elsewhere.
  • Two-factor authentication. Nginx UI supports TOTP and passkeys (Account, then 2FA / Passkeys). Once enabled, sensitive actions ask for a second factor. Passkeys need a DNS name (Step 8).
  • More users. Add users under Users. Every Nginx UI user can change the nginx configuration of this server.
  • Lockout. After MaxAttempts wrong passwords from one address (10 by default, [auth] in /etc/nginx-ui/app.ini), Nginx UI blocks that address for BanThresholdMinutes. nginx also limits the login routes to 10 attempts a minute per address.
12

Versions, provenance and file locations

nginx-ui --version | head -1 sed -n '1,2p' /usr/share/doc/nginx-ui/BUILD-PROVENANCE.txt grep 'golang.org/x/net' /usr/share/doc/nginx-ui/go.mod nginx -v
nginx-ui 2.9.1 2(576) 35e500f9 (go1.27.3 linux/amd64) Nginx UI 2.9.1 on this image was compiled by sac from the unmodified upstream source with one dependency raised (golang.org/x/net v0.62.0). Nginx UI is licensed under the GNU AGPL-3.0 (LICENSE and SOURCE in this directory). golang.org/x/net v0.62.0 nginx version: nginx/1.24.0 (Ubuntu)

Why a source build. Nginx UI 2.9.1 is the current upstream release. Two advisories in the Go networking library it uses (GO-2026-6860 and GO-2026-6866, golang.org/x/net/html) were published after it. The vulnerability scanner of the Go project finds the first one reachable in the official release binary.

SecureAnyCloud compiled the unmodified v2.9.1 source with upstream's own toolchain and build command and upstream's own (signature-verified) web frontend. It raised only golang.org/x/net to the fixed v0.62.0. /usr/share/doc/nginx-ui/BUILD-PROVENANCE.txt lists every input and checksum. The in-app Upgrade page installs upstream's official release binaries.

Known scanner finding. Scanners report GO-2026-4883 and GO-2026-4887 against the github.com/docker/docker module that Nginx UI links for its optional "nginx in a Docker container" mode. Both describe bugs in the Docker daemon (an authorization plugin bypass and a plugin privilege check) and list no fixed version for that module. They cannot be reached here: Nginx UI only uses the Docker client library, and the image runs no Docker daemon.

Path What it is

/etc/nginx-ui/app.ini Nginx UI settings (root only); its database database.db is next to it

/root/nginx-ui-credentials.txt URL and admin password (root only)

/etc/nginx/sites-available, /etc/nginx/sites-enabled your sites, as managed by Nginx UI

/etc/nginx/conf.d/sac-nginx-ui.conf the HTTPS front door for Nginx UI; certificate and key in /etc/nginx/sac/tls/

/run/sac-nginx-ui/address.env this VM's public and private IP addresses, read from Azure IMDS at each boot

/usr/share/doc/nginx-ui/ LICENSE, SOURCE (the AGPL-3.0 source offer), BUILD-PROVENANCE.txt, go.mod and go.sum

journalctl -u nginx-ui Nginx UI's log (also nginx, nginx-ui-firstboot, nginx-ui-address)

Nginx UI's own log lists the requests it serves, including WebSocket URLs that carry a short-lived session token. Only root and the adm group can read the journal, and signing out ends those sessions.

13

If you deployed before 28 October 2026

VMs deployed from the earlier version of this image run Nginx UI 2.4.2 on plain HTTP on port 9000. The image's network rules open that port to the internet, and the browser terminal is on as a root shell.

Nginx UI 2.4.2 is affected by 30 published Nginx UI security advisories, fixed in releases 2.5.0 to 2.9.0. They include three rated critical:

  • GHSA-qq4p-5m6w-ww57: the node secret leaked into access logs.
  • GHSA-7mm5-v557-38qq: remote code execution through backup restore.
  • GHSA-p393-cf76-4jmr: remote code execution through a restored settings file.
  • GHSA-w8p3-r29g-jg3p: a static node secret giving full admin without a login.

Your VM keeps running as it is. Nothing changes on it unless you act. To check your version:

/usr/local/bin/nginx-ui --version | head -1

If it reports nginx-ui 2.4.2, do this now:

  • Close port 9000 to the internet. In the VM's network security group, delete the rule for TCP 9000, or limit its source to your own IP address. Until you redeploy, reach the interface through an SSH tunnel instead (ssh -L 9000:127.0.0.1:9000 azureuser@<public-ip>, then http://127.0.0.1:9000/).
  • Upgrade in place to 2.9.1 (the official release, checked against its published SHA-256):
cd /tmp && curl -fsSLO https://github.com/0xJacky/nginx-ui/releases/download/v2.9.1/nginx-ui-linux-64.tar.gz echo '8caa38db01a1b97b742c07015d0e19269c2cd13fdcd08aa9e948a5435d35fb08 nginx-ui-linux-64.tar.gz' | sha256sum -c - tar -xzf nginx-ui-linux-64.tar.gz nginx-ui
sudo systemctl stop nginx-ui
sudo cp -a /usr/local/bin/nginx-ui /usr/local/bin/nginx-ui.2.4.2.bak && sudo cp -a /usr/local/etc/nginx-ui /usr/local/etc/nginx-ui.2.4.2.bak
sudo install -o root -g root -m 0755 nginx-ui /usr/local/bin/nginx-ui && rm -f nginx-ui nginx-ui-linux-64.tar.gz
sudo systemctl start nginx-ui && /usr/local/bin/nginx-ui --version | head -1
  • Turn the browser terminal off. Set StartCmd = /bin/false under [terminal] in /usr/local/etc/nginx-ui/app.ini, then run sudo systemctl restart nginx-ui.
  • Rotate the secrets the old version may have exposed. Change the admin password in the web interface (and in /root/nginx-ui-credentials.txt). Then give Nginx UI a new node secret and session secret, which signs out every user:
sudo python3 -I -c ' import re, secrets p = "/usr/local/etc/nginx-ui/app.ini" s = open(p).read() s = re.sub(r"(?m)^(JwtSecret\s*=).*$", lambda m: m.group(1) + " " + secrets.token_hex(32), s, count=1) s = re.sub(r"(?ms)(^\[node\].*?^Secret\s*=).*?$", lambda m: m.group(1) + " " + secrets.token_hex(32), s, count=1) open(p, "w").write(s) ' && sudo systemctl restart nginx-ui
  • Look for activity you did not cause. Check for users you did not create (Users), sites, streams or configuration you did not write, and requests in sudo journalctl -u nginx-ui to /api/restore, /api/backup, /api/install or /mcp that were not yours. If you find any, treat the VM as compromised: build a new one from the current image and move your sites to it.

You can also redeploy from the current image version. This is recommended, because it adds HTTPS on 443, Nginx UI on localhost only, the metadata block and the rest of this guide.

Copy your sites across with scp from /etc/nginx/sites-available, /etc/nginx/streams-available and your certificates (/etc/nginx/ssl if you issued them in Nginx UI). Enable them on the new VM in Manage Sites, then delete the old VM.

You do not need to contact anyone to do any of this. SecureAnyCloud support is available if you want help (sales@secureanycloud.com).

14

Backup and maintenance

  • Back up /etc/nginx (your sites, streams and certificates) and /etc/nginx-ui (Nginx UI's settings and database). Azure Backup of the VM's OS disk covers both. Nginx UI also has its own Backup page and scheduled Auto Backup to a local directory or S3-compatible storage.
  • Ubuntu security updates, including nginx, install automatically (unattended-upgrades). Install Nginx UI updates from its Upgrade page (upstream's official release binaries) or by deploying a newer image version.
15

Security recommendations

  • Limit TCP 443 in the network security group to the networks that need the web interface and your sites, and TCP 22 to your admin network. Nginx UI controls this server's web configuration, so treat it as an administrative console, ideally reachable over a VPN, Azure Bastion or a private network only. If your sites must be public, consider serving them on this VM and reaching Nginx UI only through the SSH tunnel (Step 6).
  • Change the admin password on first sign-in, switch on two-factor authentication and give each person their own user (Step 11).
  • Serve the web interface under your own DNS name with a trusted certificate (Step 8).
  • Leave the browser terminal off unless you need it (Step 9), and keep Nginx UI on 127.0.0.1:9000.

Support and licensing

SecureAnyCloud provides 24/7 technical support for this image by email and live chat: sales@secureanycloud.com.

Nginx UI is licensed under the GNU Affero General Public License v3.0. Its licence and the offer of its complete corresponding source are on the VM in /usr/share/doc/nginx-ui/ (LICENSE and SOURCE). The source is the upstream tag v2.9.1 with the go.mod and go.sum shipped in that directory, and SecureAnyCloud provides it on request. nginx is licensed under the 2-clause BSD licence.

NGINX is a trademark of F5, Inc. SecureAnyCloud is not affiliated with or endorsed by F5, Inc. or the Nginx UI project.

Deploy on Azure

Launch Nginx UI on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure