Networking AWS

Zoraxy Reverse Proxy on AWS User Guide

| Product: Zoraxy on AWS

Overview

This image runs Zoraxy, a self hosted reverse proxy with a full web management interface. Rather than hand editing configuration files, you define virtual hosts, upstream targets, redirect rules, access control lists and TLS certificates from a browser, and Zoraxy applies them live.

Zoraxy is distributed under the GNU Affero General Public License version 3. It is installed from the official prebuilt single Go binary at /usr/local/bin/zoraxy, with a copy of the licence at /usr/share/doc/zoraxy/LICENSE. There is no external database: Zoraxy keeps its state in an embedded key value store.

The service runs under systemd and starts on boot. All state, including the configuration store, the node identity, the certificate store, request logs and plugins, lives under /opt/zoraxy, which is a dedicated and independently resizable EBS data volume.

The security model, which differs from a stock Zoraxy install

Two ports carry your traffic and are the only application ports this image expects to be open:

Port Purpose
80 Proxied sites over HTTP. Redirects to HTTPS unless a rule opts out.
443 Proxied sites over HTTPS.

The management console is bound to the loopback address 127.0.0.1:8000 and is deliberately not published. It is not in the security group and cannot be reached from outside the instance at all. You open it through an SSH tunnel from your own workstation, which Step 4 walks through. This closes off the most common failure mode for a self hosted proxy, which is an administrative console left facing the internet.

Every instance generates its own administrator password at first boot, writes it to /root/zoraxy-credentials.txt with mode 0600, and registers it through the application's own first run account window before that window closes. No credential, account store, node identity, proxy rule, TLS private key or certificate authority account key is baked into the image, so two instances launched from it never share a secret.

Two upstream defaults are switched off in this image because neither is appropriate for a machine image: the mDNS scanner and transponder, which would otherwise probe and advertise on your network, and the built in static web server, which upstream starts automatically on port 5487 across all interfaces with directory listing enabled. Both remain available and can be turned back on from the console if you want them.

Prerequisites

Before you deploy this image you need:

  • An Amazon Web Services account where you can launch EC2 instances
  • IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
  • An EC2 key pair in the target Region for SSH access to the instance
  • A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network, and ports 80 and 443 from wherever your proxied sites should be reachable
  • An SSH client on your workstation, which you also use to tunnel to the management console
  • The AWS CLI (version 2) installed locally if you plan to deploy from the command line

Step 1: Launch the Instance from the AWS Marketplace

Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images choose AWS Marketplace AMIs and search for Zoraxy. Select the cloudimg listing and choose Select, then Continue on the subscription summary.

Pick an instance type of m5.large or larger. Choose your EC2 key pair under Key pair (login). Under Network settings select your VPC and subnet, and either create or select a security group that opens port 22 from your management network and ports 80 and 443 for your proxied traffic. Do not open port 8000: the management console is bound to loopback and does not listen on the instance's public address.

Select Launch instance. First boot initialisation completes a few seconds after the instance state becomes Running and the status checks pass.

Step 2: Launch the Instance from the AWS CLI

The following block launches an instance from the cloudimg Zoraxy Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens ports 22, 80 and 443 as described above.

aws ec2 run-instances \
  --image-id <ami-id> \
  --instance-type m5.large \
  --key-name <key-name> \
  --subnet-id <subnet-id> \
  --security-group-ids <security-group-id> \
  --block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":30,"VolumeType":"gp3"}}]' \
  --metadata-options 'HttpTokens=required,HttpEndpoint=enabled' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=zoraxy}]'

Wait for the instance to reach the running state and pass both status checks before continuing.

Step 3: Connect to Your Instance

Connect over SSH on port 22 using the login user for your operating system variant.

Connecting to your instance

OS variant SSH login user
Ubuntu 24.04 ubuntu
ssh -i /path/to/your-key.pem ubuntu@<public-ip>

The login banner tells you where this instance's credentials are and how to reach the console.

Confirm the service is running and check the version:

STATE=$(systemctl is-active zoraxy.service)
echo "zoraxy.service is ${STATE}"
[ "${STATE}" = "active" ] || exit 1
VER=$(/usr/local/bin/zoraxy -version | head -1)
echo "${VER}"
case "${VER}" in *3.3.3*) echo "version check passed" ;; *) echo "unexpected version"; exit 1 ;; esac

Real output from a running instance:

zoraxy.service is active
Zoraxy - Version 3.3.3
version check passed

Confirm what is actually listening, and in particular that the management console is on loopback only:

LISTEN=$(sudo ss -tlnH | awk '{print $4}' | sort)
echo "${LISTEN}"
case "${LISTEN}" in
  *"127.0.0.1:8000"*) echo "management console is bound to loopback" ;;
  *) echo "management console is not on loopback"; exit 1 ;;
esac
case "${LISTEN}" in
  *"0.0.0.0:8000"*|*"[::]:8000"*|*"*:8000"*) echo "management console is publicly bound"; exit 1 ;;
esac
case "${LISTEN}" in
  *":5487"*) echo "the static web server is running"; exit 1 ;;
  *) echo "the built in static web server is off, as shipped" ;;
esac

Real output from a running instance:

*:443
*:80
0.0.0.0:22
127.0.0.1:8000
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
management console is bound to loopback
the built in static web server is off, as shipped

Ports 80 and 443 are your proxied data plane. Port 8000 is bound to 127.0.0.1 and is unreachable from anywhere but the instance itself.

Confirm the dedicated data volume is mounted:

findmnt -no SOURCE,TARGET,FSTYPE /opt/zoraxy
findmnt -no TARGET /opt/zoraxy | grep -qx /opt/zoraxy || exit 1
echo "/opt/zoraxy is a dedicated filesystem"

Real output from a running instance:

/dev/nvme1n1 /opt/zoraxy ext4
/opt/zoraxy is a dedicated filesystem

Step 4: Retrieve the Credentials and Open the Management Console

This instance's administrator credentials were generated on this machine at first boot. List the keys held in the credentials file:

sudo grep -oE '^[A-Za-z._]+=' /root/zoraxy-credentials.txt
sudo stat -c 'perms=%a owner=%U:%G' /root/zoraxy-credentials.txt

Real output from a running instance:

zoraxy.admin.user=
zoraxy.admin.pass=
ZORAXY_ADMIN_URL=
ZORAXY_PUBLIC_URL=
perms=600 owner=root:root

To read the values, including the password, run the following on the instance. Only root can read the file.

sudo cat /root/zoraxy-credentials.txt

The management console listens on 127.0.0.1:8000 and is not reachable from the internet. From your own workstation, open an SSH tunnel to it:

ssh -i /path/to/your-key.pem -L 8000:127.0.0.1:8000 ubuntu@<public-ip>

Leave that session open and browse to http://127.0.0.1:8000/ on your workstation. Sign in with the username and password from the credentials file.

Zoraxy sign in page

After signing in you land on the status view: proxy service state, the port it is serving on, request counters and network throughput, with the full feature menu down the left hand side.

Zoraxy status dashboard

Verifying the console rejects the wrong callers

Before you rely on the console, prove it is actually protected. An unauthenticated call to an administrative endpoint is redirected to the login page and never answered with data:

CODE=$(curl -s -o /dev/null -w '%{http_code}' -m 10 http://127.0.0.1:8000/api/proxy/list)
echo "unauthenticated GET /api/proxy/list returned HTTP ${CODE}"
case "${CODE}" in
  200) echo "the management API answered an unauthenticated caller"; exit 1 ;;
  "") echo "no response from the management API"; exit 1 ;;
  *)   echo "the management API refused the unauthenticated request, as expected" ;;
esac

Real output from a running instance:

unauthenticated GET /api/proxy/list returned HTTP 307
the management API refused the unauthenticated request, as expected

Now prove this instance's own credential works. The block below reads the username and password out of the root only credentials file at the moment it runs and never prints them. Zoraxy protects its console with a session cookie plus a cross site request forgery token, so the login exchange fetches the token first.

WORK=$(mktemp -d)
JAR="${WORK}/jar"
CSRF=$(curl -s -m 10 -c "${JAR}" http://127.0.0.1:8000/login.html \
        | sed -n 's/.*name="zoraxy\.csrf\.Token" content="\([^"]*\)".*/\1/p' | head -1)
[ -n "${CSRF}" ] || { echo "could not read the login page token"; rm -rf "${WORK}"; exit 1; }
USER=$(sudo grep '^zoraxy.admin.user=' /root/zoraxy-credentials.txt | cut -d= -f2-)
PASS=$(sudo grep '^zoraxy.admin.pass=' /root/zoraxy-credentials.txt | cut -d= -f2-)
[ -n "${USER}" ] && [ -n "${PASS}" ] || { echo "could not read the credentials"; rm -rf "${WORK}"; exit 1; }
RESP=$(curl -s -m 15 -b "${JAR}" -c "${JAR}" -H "X-CSRF-Token: ${CSRF}" \
        --data-urlencode "username=${USER}" \
        --data-urlencode "password=${PASS}" \
        http://127.0.0.1:8000/api/auth/login)
case "${RESP}" in
  *'"OK"'*) echo "the management login accepted this instance's own credential" ;;
  *) echo "the management login rejected the credential"; rm -rf "${WORK}"; exit 1 ;;
esac
CODE=$(curl -s -o /dev/null -w '%{http_code}' -m 10 -b "${JAR}" http://127.0.0.1:8000/api/proxy/list)
rm -rf "${WORK}"
echo "authenticated GET /api/proxy/list returned HTTP ${CODE}"
[ "${CODE}" = "200" ] || exit 1

Real output from a running instance:

the management login accepted this instance's own credential
authenticated GET /api/proxy/list returned HTTP 200

Step 5: Create Your First Reverse Proxy Rule

In the console open Reverse Proxy, then Create Proxy Rule. Enter the hostname you intend to publish under Matching Keyword / Domain, and the address of the service you want to sit behind it under Target IP Address or Domain Name with port, for example 127.0.0.1:3000. Tick Proxy Target require TLS Connection only if the upstream itself speaks HTTPS. Select Create Endpoint.

Creating a new proxy rule in Zoraxy

The rule appears in HTTP Proxy, where you can toggle it on and off, attach virtual directories, add tags, and open the detail editor for headers, rate limiting, sticky sessions and access rules. Subdomains and wildcards are supported, and a comma separated list adds alias hostnames to the same rule.

The Zoraxy HTTP proxy rule list

Point a DNS record for that hostname at your instance's public IP address and the site is live on ports 80 and 443.

Proving a real request travels through the proxy

The image ships a self test that exercises the whole path end to end. It stands up a temporary upstream on the loopback interface carrying a random marker, creates a proxy rule for it through the authenticated API, requests it through the public data plane on port 80, and checks the marker comes back. It removes the temporary rule and the temporary upstream before it exits.

sudo /usr/local/sbin/zoraxy-selftest.sh

Real output from a running instance:

  1/4 unauthenticated admin API refused (HTTP 307)
  2/4 wrong password refused, no session granted (HTTP 307)
  3/4 per-instance credential authenticates, admin API reachable (HTTP 200)
  4/4 reverse-proxy round-trip served the upstream body through port 80
SELFTEST OK - admin API protected, per-instance credential valid, proxy round-trip live

Confirm the self test left nothing behind:

LEFT=$(sudo find /opt/zoraxy/conf/proxy -name '*selftest*' 2>/dev/null | wc -l | tr -d ' ')
echo "temporary self test rules remaining: ${LEFT}"
[ "${LEFT}" = "0" ] || exit 1
echo "the self test removed its temporary rule"

Real output from a running instance:

temporary self test rules remaining: 0
the self test removed its temporary rule

Step 6: Enable HTTPS for Your Sites

Open Security, then TLS / SSL certificates. You can upload an existing certificate and private key for a domain, paste them directly, or let Zoraxy obtain one for you.

The Zoraxy TLS and SSL certificate view

For an automatically issued certificate, open Security, then ACME Client, choose your certificate authority, enter the domain and the contact email address, and request the certificate. The domain must already resolve to this instance and port 80 must be reachable from the internet so the challenge can complete. Zoraxy renews certificates automatically before they expire, and the renewal policy is configurable on the same page.

Until a certificate exists for a hostname, Zoraxy serves it with a self signed fallback certificate that it generates on this instance. No certificate and no certificate authority account key ships in the image.

Step 7: Restrict Who Can Reach Your Sites

Open Access & Connections, then Access Control. Every proxy rule uses the default access rule unless you assign it another one, so you can apply one policy estate wide or write a rule per site.

Zoraxy access control lists

Each rule carries a blacklist, a whitelist and a quick ban list. You can block or allow by IP address, by IP range, or by the country estimated from the requester's address. Country estimation is based on IP geolocation and is not exact, so treat it as a coarse control rather than a precise one. Create a new rule with Access Rule Editor and attach it to a site from the rule's detail editor in HTTP Proxy.

Trusted Proxy, on the same menu, tells Zoraxy which upstream addresses it should trust for the X-Forwarded-For header. Set this if the instance itself sits behind a load balancer or CDN, otherwise your access rules will see the intermediary's address rather than the real client's.

Step 8: Operations and Maintenance

Service control

sudo systemctl status zoraxy
sudo systemctl restart zoraxy

Reading the service log

sudo journalctl -u zoraxy.service -n 100 --no-pager

Zoraxy also keeps its own request and system logs under /opt/zoraxy/log/, and the console exposes them under Others, then Utilities.

Where the state lives

Everything Zoraxy needs is under /opt/zoraxy on the dedicated data volume:

Path Contents
/opt/zoraxy/sys.db Account store, settings and the session key
/opt/zoraxy/sys.uuid Node identity and the cross site request forgery signing key
/opt/zoraxy/conf/ Proxy, redirect, stream and access rules, plus the certificate store
/opt/zoraxy/log/ Request and system logs
/opt/zoraxy/plugins/ Installed plugins, empty as shipped

Backup

Stop the service so the embedded store is quiesced, archive the data volume contents, then start it again. Run this on the instance, substituting a destination of your own:

sudo systemctl stop zoraxy
sudo tar -czf /var/backups/zoraxy-$(date +%F).tar.gz -C /opt zoraxy
sudo systemctl start zoraxy

The archive contains your certificates and your account store, so treat it as a secret and store it accordingly. For a full disaster recovery position, take an EBS snapshot of the /opt/zoraxy volume instead, which you can restore to a new instance directly.

Resizing the data volume

The data volume is independent of the operating system disk. Modify it in the EC2 console or with aws ec2 modify-volume, then grow the filesystem on the instance with sudo resize2fs against the device that findmnt /opt/zoraxy reports.

Changing the administrator password

Sign in to the console, open the account menu at the top right, and change the password there. If you ever lose the credential entirely, the shipped binary can reset the account store: stop the service, run the binary's account reset from the working directory, then start the service and create a new administrator on the next visit.

Enabling the features shipped switched off

The mDNS scanner is disabled by a flag on the service unit. To re-enable it, edit ExecStart in /etc/systemd/system/zoraxy.service, remove -mdns=false, then sudo systemctl daemon-reload && sudo systemctl restart zoraxy. The static web server is disabled per instance and is re-enabled from Services, then Static Web Server, where you can also keep it pinned to the loopback interface and publish it through a proxy rule instead of exposing it directly.

Troubleshooting

The console will not load in your browser. The console is bound to loopback. Check that the SSH tunnel from Step 4 is still open, and that you are browsing to http://127.0.0.1:8000/ on your workstation rather than to the instance's public address. Opening port 8000 in the security group will not help, because nothing is listening on the public interface.

A proxied site returns a redirect to HTTPS you did not expect. Zoraxy redirects port 80 to port 443 by default. Open the rule's detail editor in HTTP Proxy and allow plain HTTP access for that host if you want it served on port 80 directly.

A proxied site returns a gateway error. The upstream address in the rule is not answering. Check the service you pointed at is running and listening on the address and port in the rule, and that the instance can reach it.

A certificate request fails. The domain must resolve to this instance and port 80 must be reachable from the internet for the challenge. Check the DNS record and the security group, then retry from ACME Client.

The service will not start. Read the log with sudo journalctl -u zoraxy.service -n 100 --no-pager. A common cause is another process already holding port 80 or 443 on the instance.

Support

cloudimg provides 24/7 technical support for this image by email at support@cloudimg.co.uk and by live chat on cloudimg.co.uk. Our engineers help with deployment, proxy rule design, upstream and load balancing configuration, TLS and certificate troubleshooting, access control, and data volume resizing.

Zoraxy itself is an open source project by tobychui, licensed under the GNU Affero General Public License version 3. Product documentation and the source are at github.com/tobychui/zoraxy.