Networking AWS

Nginx UI 2.9.3 on AWS User Guide

| Product: Nginx UI 2.9.3 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using Nginx UI 2.9.3 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other VPN, proxy and remote access servers in the listing are described on the listing's guide page.

Nginx UI is a web interface for nginx. From a browser you create and edit sites, streams and upstreams, edit any nginx configuration file with syntax checking, test, reload and restart nginx, 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.

What the cloudimg image adds:

  • 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 instance a one-time service generates an admin password unique to the instance, completes Nginx UI's first-run setup through its local API with the network shut off, proves the password works and that guessable ones are refused, and only then opens 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 instance. Port 80 redirects to HTTPS, and a request for a host name the instance 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 listing's security group does not include it. If anyone moves Nginx UI's listener (Preferences, or an edit of its settings file), the start-up check refuses to start Nginx UI until it is back on 127.0.0.1:9000, so it can never listen on the network.
  • The browser terminal is off. Nginx UI includes a terminal that is a shell on the instance in your browser. It is disabled on this image; Step 8 explains how to turn it on if you want it, 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), 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.
  • Instance metadata blocked. Nginx UI and nginx cannot reach the EC2 instance metadata service (169.254.169.254 and fd00:ec2::254). 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 instance's metadata or its IAM role credentials. The AMI is registered IMDSv2 only.
  • The current Nginx UI release. Nginx UI 2.9.3, outside every published Nginx UI security advisory (69 at the time of the build, including the critical ones that affect releases before 2.5.0) and carrying the 2.9.2 fixes for read-only service tokens, certificate path validation and the login lockout. cloudimg compiles it from the unmodified upstream source with one newer Go dependency (details in Step 11).
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Nginx UI 2.9.3 (built from the v2.9.3 source with Go 1.27.3), nginx 1.24 (from Ubuntu 24.04), cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3

The Nginx UI dashboard over HTTPS showing the server's CPU, memory, disk and network and the nginx status

Security posture

  • Per-instance secrets only. The admin password, Nginx UI's session signing secret, its node secret, its encryption key and the TLS key are all generated on your instance's first boot. Two instances launched from the same AMI share none of them.
  • Fails closed. Nginx UI and nginx start only after first boot has written this instance's configuration and a ready marker, and a start-up check refuses an uninstalled configuration, a leftover install secret, demo mode or a listener off the loopback address. If first boot cannot finish, the web interface stays closed and first boot tries again on the next boot.
  • End-entity certificate. The self-signed certificate can only vouch for this instance's own names (it is not a certificate authority). Compare its fingerprint with the one in the credentials file, or replace it with a CA-issued certificate (Step 7). Do not add it to a trust store.
  • Authorization header, not cookies. Nginx UI authenticates every API call with the Authorization header (and a WebSocket with a short-lived token in its URL); a browser cookie alone never logs in, so a link on another site cannot act with your session.
  • Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.

Security notes for Nginx UI 2.9.3. Go's vulnerability scanner reports two advisories in the Docker client module that Nginx UI links for its optional "nginx in a Docker container" mode (GO-2026-4883 and GO-2026-4887). Both describe bugs in the Docker daemon, list no fixed version, and cannot be reached here: the image runs no Docker daemon and has no Docker socket. The official 2.9.3 release binary also links a version of golang.org/x/net affected by GO-2026-6860 (slow HTML parsing of a DNS provider's API response); the binary on this image is built with the fixed v0.62.0 instead (Step 11).

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A 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, plus 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.

Recommended instance type: m5.large. Nginx UI and nginx are light; size the instance for the sites nginx serves.

Connecting to your instance

Version Operating system SSH user
Nginx UI 2.9.3 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Nginx UI 2.9.3 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended) and your key pair.
  5. Security group: the listing's default group allows SSH (22), HTTP (80) and HTTPS (443). Restrict SSH, and preferably 443, to your own addresses.
  6. Choose your VPC and a public subnet (or attach an Elastic IP later), leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH and HTTPS from your management address and HTTP from everyone, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR range with your own:

SG=$(aws ec2 create-security-group --group-name nginx-ui \
  --description "Nginx UI" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22  --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80  --cidr 0.0.0.0/0
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=nginx-ui}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required. First boot takes under a minute after the instance is running.

Step 2: Connect and check the services

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
systemctl is-active nginx-ui nginx
sudo test -f /var/lib/cloudimg/nginx-ui-firstboot.done && echo FIRSTBOOT_DONE
active
active
FIRSTBOOT_DONE

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"))

(nginx starts one worker per vCPU, so the number of nginx entries depends on the instance type.)

Step 3: Get your URL and admin password

The URL and the admin password are in a file only root can read:

sudo cat /root/nginx-ui-credentials.txt
# Nginx UI 2.9.3 - generated on first boot by nginx-ui-firstboot.service.
# Every value below is unique to this instance; nothing was baked into the image. Keep this file private.

# Web interface (HTTPS, self-signed certificate unique to this instance): 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/cloudimg/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://www.cloudimg.co.uk/guides/nginx-ui-2-9-3-on-ubuntu-24-04-aws/
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 instance's public IPv4 address as the instance metadata service reports it (the private address when the instance has no public IP). The certificate also names the private IP address and, when the instance has one, its public DNS name. To serve Nginx UI under your own DNS name, see Step 7.

Step 4: Sign in

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

The Nginx UI sign-in page served over HTTPS

Change the password at once from the account menu at the top right (Account), then update the file so the self-test keeps working (Step 10). The dashboard (shown at the top of this guide) reports the server's load and the nginx status.

Step 5: Manage nginx

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

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

The Manage Sites page listing the disabled default site

The site editor showing the stock default site with its configuration

How this fits with the HTTPS front door:

  • The server blocks that serve Nginx UI itself are in /etc/nginx/conf.d/cloudimg-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 instance's own names (the public and private addresses, or your own name after Step 7); 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 cloudimg-nginx-ui.conf. Editing or removing that file deliberately can take the web interface off line. If that happens, reach Nginx UI through an SSH tunnel and fix it there:
ssh -i ~/.ssh/my-key-pair.pem -L 9000:127.0.0.1:9000 ubuntu@<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 (the buttons on the dashboard): nginx always runs as its own nginx.service, with the same metadata block.

Step 6: Certificates

To issue Let's Encrypt certificates from Nginx UI, first set the email address for your ACME account under Preferences then 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 security group and a DNS record for the site pointing at the instance; DNS validation needs your DNS provider's API credentials (Certificates then DNS Credentials).

Step 7: Your own DNS name for the web interface

Point a DNS record (for example a Route 53 A record for an Elastic IP) at the instance, then tell the image to serve Nginx UI under that name. This issues a new self signed certificate for it and updates nginx, Nginx UI's base URL and passkey settings and the credentials file:

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

sudo nginx-ui-set-host with no name returns to the instance's own IP address. To use a certificate from your own CA or from Let's Encrypt for the web interface, replace /etc/nginx/cloudimg/tls/nginx-ui.crt and /etc/nginx/cloudimg/tls/nginx-ui.key (keep the key mode 0600) and run sudo systemctl reload nginx.

Step 8: The web terminal

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

The Nginx UI terminal page showing that the web terminal is disabled on this image

sudo grep '^StartCmd' /etc/nginx-ui/app.ini
StartCmd = /usr/local/sbin/nginx-ui-terminal-disabled

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 instance. To turn it on with a login prompt (the person must sign in with a Linux account and password on the instance), set StartCmd = login under [terminal] in /etc/nginx-ui/app.ini and restart Nginx UI:

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.

Step 9: Security checks

nginx-ui-selftest re-proves the appliance with this instance's credentials: the services, Nginx UI on 127.0.0.1:9000 only, HTTPS with this instance's certificate, the setup window closed and refused at nginx, the API refusing anonymous calls, a wrong and the default password refused, the password in the credentials file accepted, and Nginx UI reading the nginx status:

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

The metadata block is a systemd IPAddressDeny rule on Nginx UI and nginx, covering the instance metadata service on IPv4 and IPv6:

for u in nginx-ui nginx; do printf '%s: ' $u; systemctl show -p IPAddressDeny --value $u.service; done
nginx-ui: 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32
nginx: 168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32

The front door, from the instance itself (a state-changing request from another site's page, the setup routes, the setup status, an anonymous API call, the HTTP redirect and a request for a host name the instance does not serve):

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

The last line is nginx closing the connection without an answer (444) for a name it does not serve.

Step 10: 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, or 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 7).
  • 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, under [auth] in /etc/nginx-ui/app.ini) Nginx UI blocks that address for BanThresholdMinutes (10). Since 2.9.2 the lockout applies again after an earlier ban expires, and OTP verification has its own attempt limit.

Step 11: 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.3 2(579) 79785c5f (go1.27.3 linux/amd64)
Nginx UI 2.9.3 on this image was compiled by cloudimg 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.3 is the current upstream release, and it still uses golang.org/x/net v0.61.0. Two advisories in that library's HTML parser (GO-2026-6860 and GO-2026-6866) were published on 27 October 2026, and Go's vulnerability scanner finds the first one reachable in the official release binary (through the DNS provider integrations used for certificates). cloudimg compiles the unmodified v2.9.3 source with upstream's own toolchain and build command and upstream's own signature-verified web frontend, raising only golang.org/x/net to the fixed v0.62.0, and checks that the result links exactly the same modules as the official release apart from that one. /usr/share/doc/nginx-ui/BUILD-PROVENANCE.txt lists every input and checksum, and govulncheck-binary.txt next to it is the scanner's result for the shipped binary. The in-app Upgrade page installs upstream's official release binaries.

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/cloudimg-nginx-ui.conf the HTTPS front door for Nginx UI; certificate and key in /etc/nginx/cloudimg/tls/
/usr/share/doc/nginx-ui/ LICENSE, SOURCE (the AGPL-3.0 source offer), BUILD-PROVENANCE.txt, go.mod, go.sum, buildinfo.txt and govulncheck-binary.txt
journalctl -u nginx-ui Nginx UI's log (also nginx and nginx-ui-firstboot)

Nginx UI's own log lists the requests it serves, including WebSocket URLs that carry a short-lived session token. The journal is readable only by root and the adm group; signing out ends those sessions.

Step 12: Backup and maintenance

  • Back up /etc/nginx (your sites, streams and certificates) and /etc/nginx-ui (Nginx UI's settings and database). An EBS snapshot or AWS Backup of the root volume 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). Nginx UI updates are installed from its Upgrade page (upstream's official release binaries) or by launching a newer version from the listing.

Step 13: Security recommendations

  • Limit TCP 443 in the 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: treat it as an administrative console, ideally reachable over a VPN or a private network only. If your sites must be public, consider serving them on this instance and reaching Nginx UI only through the SSH tunnel (Step 5).
  • Change the admin password on first sign in, switch on two-factor authentication and give each person their own user (Step 10).
  • Serve the web interface under your own DNS name with a trusted certificate (Step 7).
  • Leave the browser terminal off unless you need it (Step 8), and keep Nginx UI on 127.0.0.1:9000.

Support and licensing

Nginx UI is open-source software by the Nginx UI project, licensed under the GNU Affero General Public License v3.0. Its licence and the offer of its complete corresponding source are on the instance in /usr/share/doc/nginx-ui/ (LICENSE and SOURCE); the source is the upstream tag v2.9.3 with the go.mod and go.sum shipped in that directory, and cloudimg provides it on request. nginx is licensed under the 2-clause BSD licence. cloudimg provides the build, the secure defaults, the per-instance credentials and certificate, the metadata block, the fail-closed start, the self-test and helpers, and 24/7 support.

cloudimg is not affiliated with or endorsed by the Nginx UI project or by F5, Inc. (owner of the NGINX trademark). The names are used here only to identify the software.

For help with deployment or configuration, email support@cloudimg.co.uk or visit cloudimg support.