Networking AWS

Outline Server 1.12.3 on AWS User Guide

| Product: Outline Server 1.12.3 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying Outline Server 1.12.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 hub.

Outline Server (also known by its container name, Shadowbox) is the server half of the open-source Outline access system. You run it on your EC2 instance, then hand access keys to people. Each person pastes their key into the free Outline Client app (Windows, macOS, Linux, iOS, Android, ChromeOS) and their traffic travels to your server over Shadowsocks, an encrypted proxy protocol, and out to the internet from your instance. You manage the server and its keys from the Outline Manager desktop app, or from the command line on the instance.

How this image differs from the upstream installer:

  • Rebuilt on a patched runtime, with no source change. The upstream 1.12.3 container image runs Node.js 18.18.0, which is affected by CVE-2024-22019 (a denial of service against the public management API), on Alpine Linux 3.18, which is end of life. This image keeps the exact 1.12.3 application (its files are byte-identical to upstream's) and rebuilds the container around it: Node.js 18.20.8 on a fully patched Alpine 3.21 (the base Outline's own development branch has moved to), Prometheus 2.53.4 LTS, and outline-ss-server 1.7.3 recompiled with Go 1.27.3 and current golang.org/x/crypto. The build recipe is in /usr/share/doc/cloudimg-outline/.
  • No Watchtower. Upstream's installer also runs Watchtower, which replaces the server image automatically and holds the Docker socket. It is not on this image: the server software changes only when you decide (Step 12).
  • Nothing pre-made. The management API secret, the TLS key and certificate are generated on your instance at first boot, and the server starts with zero access keys.
  • The image ships its container. The rebuilt image is stored on the AMI as a checksummed archive and loaded at first boot, so the instance needs no access to a container registry to start (it does need outbound internet to serve clients).

What is on the image:

  • Outline Server 1.12.3 (outline-vpn-server.service), running in Docker CE with exactly three published ports: TCP 60443 (the management API Outline Manager talks to) and TCP and UDP 60444 (the Shadowsocks port your access keys use)
  • Docker CE and containerd from Docker's own signed repository, kept up to date by Ubuntu's unattended upgrades
  • cloudimg-outline-guard.service: the firewall rule that keeps clients away from the instance metadata service
  • Helpers: outline-keys (create, list and revoke access keys; print the Outline Manager line), outline-selftest (an end-to-end test with a real Shadowsocks client), outline-imds-guard (show the metadata guard) and outline-regenerate (start over with a new server identity)

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

Security posture

  • Per-instance administrator secret. The management API has no password: it is protected by a random 128-bit path in its URL and by a self-signed certificate whose SHA-256 fingerprint Outline Manager pins. Both are generated on your instance at first boot. The line you paste into Outline Manager is written only to /root/outline-vpn-server-credentials.txt and /opt/outline/access.txt, both readable by root alone. It is never written to the system journal, the console or Docker's logs (the server's own start-up message, which includes the path, is redacted before it reaches the journal).
  • End-entity certificate. The management API's certificate can only vouch for this server (it is not a certificate authority). Do not install it in any trust store: compare its fingerprint instead (Step 5).
  • No access keys until you create them. First boot checks that the server has none.
  • Anonymous metrics off. Outline can share anonymous usage metrics with its developers. On this image that setting is off; turn it on from Outline Manager if you choose to.
  • Fail-closed start. The server cannot start before first boot has generated its secrets and loaded the metadata guard: its unit is gated on a ready marker and a preflight check. If first boot fails, the server stays stopped.
  • Metadata service protection. A connected client's traffic leaves your instance from the Outline container. Three separate walls keep it away from the instance metadata service (and from your instance's IAM role credentials): outline-ss-server refuses loopback, link-local and private destinations; a Docker firewall rule (DOCKER-USER) drops everything the container sends to 169.254.0.0/16, 168.63.129.16 and the IPv6 metadata addresses; and Docker, containerd and the Outline unit are denied the metadata service by systemd IPAddressDeny.
  • Container hardening. The container runs on Docker's private bridge network with only its three ports published (Prometheus and the internal metrics endpoints are not reachable from outside the container), with no-new-privileges, several Linux capabilities removed, no access to the Docker socket, and only its own state directory mounted. Docker listens on its local socket only.
  • Hardened SSH. Key-only login, no root login, no X11 forwarding.

Prerequisites

  • An AWS account subscribed to the listing, and an EC2 key pair in your Region.
  • A VPC subnet with internet access (a public subnet with a public IP, or a private subnet behind a NAT gateway or load balancer that forwards TCP 60443 and TCP/UDP 60444).
  • The free Outline Manager app on your computer and the Outline Client app on each device: both from https://getoutline.org/.

Connecting to your instance

Item Value
SSH user ubuntu
Management API (Outline Manager) https://<instance-address>:60443/<secret path> (from the credentials file)
Access keys (Outline Client) TCP and UDP 60444
Credentials file /root/outline-vpn-server-credentials.txt (root only)

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 Outline Server 1.12.3 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  5. Security group: the listing's default group allows SSH (TCP 22), the management API (TCP 60443) and the access-key port (TCP and UDP 60444) from anywhere. The access-key port must stay open to wherever your users are. Restrict TCP 22 and TCP 60443 to your own IP address: only you and your Outline Manager need them, and the management API is your administrator interface.
  6. Choose a subnet with internet access and a public IP, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

If you want your access keys to keep working when the instance stops and starts, attach an Elastic IP and then point the server at it (Step 9): access keys contain the server's address.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH and the management API from your address and the access-key port from anywhere, 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 address with your own:

SG=$(aws ec2 create-security-group --group-name outline-server \
  --description "Outline Server" --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 60443 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 60444 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol udp --port 60444 --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 \
  --metadata-options HttpTokens=required,HttpPutResponseHopLimit=1 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=outline-server}]'

HttpPutResponseHopLimit=1 is optional but recommended: it stops anything running inside a container on the instance from obtaining an IMDSv2 token at all, as a fourth wall behind the three described above. The image itself already makes IMDSv2 required.

Step 2: Connect and read your server details

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot finishes within a minute of launch. It generates the management API secret and the TLS certificate, loads the Outline container, applies the metadata guard and starts the server. Your server details are in /root/outline-vpn-server-credentials.txt, readable only by root. The outline.manager_config line in it is the administrator credential for your server: anyone who has it can create and revoke access keys. Keep it private.

sudo cat /root/outline-vpn-server-credentials.txt
# Outline Server 1.12.3 (cloudimg) - generated on this instance. KEEP PRIVATE: the manager line below is
# the administrator credential for this server (anyone who has it can create and revoke access keys).
# Paste the outline.manager_config value into Outline Manager (Add server > Advanced / 'Paste installation output').
# Compare the fingerprint Outline Manager shows with outline.cert_sha256. Guide: https://www.cloudimg.co.uk/guides/outline-server-1-12-3-on-ubuntu-24-04-aws/
# Ports: management API 60443 (TCP), access keys 60444 (TCP and UDP).
outline.host=203.0.113.25
outline.cert_sha256=6A0E...(64 hexadecimal characters)...B91C
outline.manager_config={"apiUrl":"https://203.0.113.25:60443/<secret path>","certSha256":"6A0E...B91C"}

The same two values are also in /opt/outline/access.txt, in the format upstream's installer prints.

Step 3: Confirm the server is running

Three services make up the server: Docker, the metadata guard and Outline itself:

systemctl is-active docker.service cloudimg-outline-guard.service outline-vpn-server.service
active
active
active

Ask the server about itself through its management API (the helper reads the secret path for you and checks the server's key):

sudo outline-keys server
{
  "name": "Outline Server (cloudimg)",
  "version": "1.12.3",
  "serverId": "375114d2-1b4f-4c22-bf24-c9978f0716f2",
  "metricsEnabled": false,
  "portForNewAccessKeys": 60444,
  "hostnameForAccessKeys": "203.0.113.25"
}

metricsEnabled: false is the anonymous metrics setting (off), and hostnameForAccessKeys is the address every access key will point at. A new server has no access keys:

sudo outline-keys list
[]

Exactly these ports are open on the instance: SSH, and the three ports Docker publishes for the container:

sudo ss -tulnp | grep -E ':(22|60443|60444) '
udp   UNCONN 0      0                0.0.0.0:60444      0.0.0.0:*    users:(("docker-proxy",...))
udp   UNCONN 0      0                   [::]:60444         [::]:*    users:(("docker-proxy",...))
tcp   LISTEN 0      4096             0.0.0.0:22         0.0.0.0:*    users:(("sshd",...))
tcp   LISTEN 0      4096             0.0.0.0:60444      0.0.0.0:*    users:(("docker-proxy",...))
tcp   LISTEN 0      4096             0.0.0.0:60443      0.0.0.0:*    users:(("docker-proxy",...))
...

Step 4: Run the self-test

outline-selftest checks the whole server with a real Shadowsocks client and a temporary access key it creates and then revokes. It needs outbound internet access for its two positive checks:

sudo outline-selftest
ok: management API answers (key pinned): Outline Server 1.12.3, anonymous metrics sharing off
ok: metadata guard loaded (DOCKER-USER drop 169.254.0.0/16 and 168.63.129.16)
ok: temporary access key created (id 2)
ok: internet egress through the proxy (TCP, checkip.amazonaws.com) answers: HTTP/1.1 200
ok: internet egress through the proxy (UDP, DNS to 1.1.1.1) answers
ok: unreachable from a Shadowsocks client through the proxy: metadata 169.254.169.254, 168.63.129.16, loopback, this instance's private address (22, 60443), the Docker gateway
ok: Amazon DNS 169.254.169.253 unreachable through the proxy (UDP)
ok: metadata unreachable from inside the Outline container's network namespace (000)
ok: the revoked temporary key no longer connects
SELFTEST_OK

Step 5: Add the server to Outline Manager

  1. Install Outline Manager on your computer from https://getoutline.org/ and open it.
  2. Choose to set up Outline on a server of your own ("Set up Outline anywhere"). Outline Manager shows the upstream install command: you do not need to run it, because the server is already installed.
  3. Print the Outline Manager line on the instance and paste it into the box Outline Manager shows (it is the whole {"apiUrl":...,"certSha256":...} value):
sudo outline-keys manager
  1. Check the certificate fingerprint. Outline Manager trusts exactly the certificate whose fingerprint is in the line you pasted. The certificate the server presents must match the fingerprint recorded on the instance:
test "$(echo | openssl s_client -connect 127.0.0.1:60443 2>/dev/null | openssl x509 -noout -sha256 -fingerprint | sed 's/.*=//; s/://g')" = "<OUTLINE_CERT_SHA256>" && echo "certificate fingerprint matches the credentials file"

Outline Manager can now rename the server, create and remove access keys, set data limits and show data usage. If Outline Manager cannot reach the server, check that your security group allows TCP 60443 from your computer's address.

Step 6: Create access keys

Create one access key per person or device, from Outline Manager (Add new key) or on the instance:

sudo outline-keys add alice
id: 1
access key (share privately, it is a credential): ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNT...@203.0.113.25:60444/?outline=1

The ss:// link is that person's credential: send it to them privately (for example with an end-to-end encrypted messenger). They paste it into the Outline Client app and choose Connect. Every key uses port 60444 on TCP and UDP, so only that port has to be reachable by your users.

Step 7: List and revoke access keys

List the keys (ids and names only; the command never prints the secrets):

sudo outline-keys list

Revoke a key by its id. The command checks that the key really is gone and fails if it is not:

sudo outline-keys rm 1

A revoked key is refused for every new connection at once; to cut off a device that is connected right now, also restart the server (sudo systemctl restart outline-vpn-server).

Step 8: Connect a device

On each device, install the Outline Client from https://getoutline.org/, choose Add server and paste that device's ss:// access key. While connected, the device's traffic goes through your EC2 instance: websites see your instance's public address.

Step 9: Elastic IP or a DNS name

Access keys and the Outline Manager line contain the address the server had at first boot. If you attach an Elastic IP, or want keys to use a DNS name, point the server at it:

sudo outline-keys hostname vpn.example.com

New and existing access keys then use that address (users re-add their key in the Outline Client, or you send them the updated key from Outline Manager), and the credentials file and Outline Manager line are rewritten. The TLS certificate and its fingerprint do not change, so Outline Manager keeps trusting the server.

Step 10: Metadata access is blocked

outline-imds-guard shows the firewall rules that drop everything the Outline container sends to the metadata addresses, with packet counters:

sudo outline-imds-guard show
Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       0    --  *      *       0.0.0.0/0            168.63.129.16        /* cloudimg-outline-imds */
    0     0 DROP       0    --  *      *       0.0.0.0/0            169.254.0.0/16       /* cloudimg-outline-imds */
Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DROP       0    --  *      *       ::/0                 fd00:ec2::123        /* cloudimg-outline-imds */
    0     0 DROP       0    --  *      *       ::/0                 fd00:ec2::253        /* cloudimg-outline-imds */
    0     0 DROP       0    --  *      *       ::/0                 fd00:ec2::254        /* cloudimg-outline-imds */

The Outline server cannot run without the guard: outline-vpn-server.service requires cloudimg-outline-guard.service, and when Docker restarts, the guard is applied again before the server. Docker, containerd and the Outline unit are also denied the metadata service directly:

systemctl show -p IPAddressDeny docker.service containerd.service outline-vpn-server.service
IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32

IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32

IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32

Clients also cannot reach anything on your instance or in your VPC through the server: outline-ss-server refuses loopback, link-local and private (RFC 1918, CGNAT and IPv6 unique-local) destinations, and the self-test proves it (Step 4).

Step 11: How the fail-closed start works, and starting over

outline-vpn-server.service starts only after first boot has written /var/lib/cloudimg/outline-bootstrap-ready, and its preflight (/usr/local/sbin/outline-preflight) refuses to start the container unless the secret file is root-only, the API secret and the end-entity certificate exist, the metadata guard is loaded and the pinned image is the one loaded. The container receives its secrets through a root-only environment file, never on a command line.

To start over with a completely new server identity (a new management API secret, a new certificate and fingerprint, and no access keys: every existing key stops working), run:

sudo outline-regenerate --yes

Then add the server to Outline Manager again with the new line from sudo outline-keys manager.

Step 12: Updates

Ubuntu's unattended upgrades install security updates daily, from the Ubuntu archive and from Docker's repository (the image adds Docker to the allowed origins in /etc/apt/apt.conf.d/51cloudimg-docker-unattended). When Docker is upgraded the Outline server restarts with it, and the metadata guard is applied again first.

The Outline server software itself never changes automatically (there is no Watchtower). Outline releases new server versions rarely; when cloudimg publishes a newer Outline version in this listing, move to it by launching the new version and recreating your keys, or keep running this one. The image identity is recorded in /etc/outline/outline.conf and checked at every start.

Step 13: Logs

The server's warnings and errors go to the journal (informational messages are turned down, and the management API path is redacted):

sudo journalctl -u outline-vpn-server.service --no-pager -n 20

First-boot progress is in sudo journalctl -u outline-vpn-server-firstboot.service.

Step 14: Security recommendations

  • Restrict TCP 22 and TCP 60443 in the security group to your own address. Keep TCP and UDP 60444 open to your users.
  • Keep the Outline Manager line private; treat it like an administrator password.
  • Give each person their own access key, and revoke keys you no longer need (Step 7). Set data limits from Outline Manager if you need them.
  • Use an Elastic IP so keys survive a stop and start (Step 9).
  • Launch with HttpPutResponseHopLimit=1 (Step 1, Option B) for an extra metadata wall.
  • Keep unattended upgrades on, so Docker and the operating system stay patched.

Support and licensing

Outline Server and outline-ss-server are licensed under the Apache License 2.0; Prometheus is Apache-2.0 and Node.js is MIT-licensed. The rebuild recipe (Dockerfile, go.mod, go.sum) and the provenance of every component are in /usr/share/doc/cloudimg-outline/, and the source is available from the upstream projects (https://github.com/Jigsaw-Code/outline-server) and from cloudimg on request. The server's IP-to-country and IP-to-ASN data is IP Geolocation by DB-IP (https://db-ip.com), licensed under Creative Commons Attribution 4.0; the container refreshes the free DB-IP Lite databases weekly. cloudimg support covers deploying and operating this image; contact support@cloudimg.co.uk.

Outline and Jigsaw are trademarks of Google LLC. cloudimg is not affiliated with or endorsed by Google, Jigsaw or the Outline Foundation.