Outline VPN server setup on your own VPS
Outline VPN server setup on your own VPS: install the Shadowsocks server, share access keys with data limits, add prefix disguises and see where Outline still works.
Outline consists of a Shadowsocks-based server, the Outline Manager app for the administrator and clients for every major platform. Jigsaw created the project; its repositories now live under the OutlineFoundation organization on GitHub. Current as of September 2026: shadowbox server 1.12.3, Outline clients 1.21.
What you'll need
- A ForestsNet Cloud VPS in the Netherlands or Germany running Ubuntu 22.04/24.04 or Debian 12.
- SSH access as
rootor a user withsudo— see VNC console and first connection. - A domain with an A record pointing at the server — optional, but with it you can move to a new IP without reissuing keys.
- Outline Manager for Windows, macOS or Linux — also optional: everything it does is available through the API.
What Outline is and when it fits
How it works
The server is a Docker container called shadowbox. Inside it run outline-ss-server (Shadowsocks written in Go), a Node.js management API and Prometheus for traffic accounting. The API listens on a separate HTTPS port with a self-signed certificate and only answers requests that include a secret path. A second container, watchtower, checks for a new image every hour.
An access key is an ss:// link carrying the address, port, cipher (chacha20-ietf-poly1305 by default) and password; normally all keys share one port. The server never replies to data it can't decrypt, holding such connections open until a timeout, and it drops replayed handshakes — that's its defence against active probing. There are no user activity logs; Prometheus keeps traffic metrics for 31 days.
How resilient it is in 2025–2026
Shadowsocks is a "fully encrypted" protocol: from the very first byte the stream looks like random noise. Censors can't decrypt it, so they detect it heuristically, and each country does it differently:
- China. Since November 2021 the Great Firewall has let through traffic that resembles a known protocol or starts with printable characters, and blocked the remaining "noise". Outline's answer was the connection prefix covered in step 5.
- Iran. During the June 2025 shutdown the national gateway allowed only DNS, HTTP and HTTPS, and DPI dropped everything else. After the January 2026 blackout, access came back on an allowlist of specific services — a regime virtually no self-hosted protocol gets through.
- Russia. Since spring 2024 the TSPU boxes have detected Shadowsocks and VMess by the traces of a TLS handshake inside the tunnel: the client sends at least three packets of 411 bytes or more, and the server sends packets more often than the client. A prefix only changes the first bytes, while this check looks at packet sizes and order. In August 2024 users on ntc.party reported that prefixes helped partially or not at all on MegaFon, Beeline, Tele2 and Rostelecom; in July–August 2025 many Rostelecom, Beeline, MegaFon and Yota subscribers saw Outline stop working.
- Protocol-agnostic measures. Since June 2025 users in Russia have reported TCP connections to foreign hosting subnets freezing after 15–20 KB of data. During mobile shutdowns Russian carriers switch to whitelists, and no VPN to a foreign server gets through directly.
Meanwhile the server side of Outline has barely changed: there has been no server release since February 2025, and the stable shadowbox 1.12.3 bundles outline-ss-server 1.7.3, which has no WebSocket support. Development goes into the client and the SDK.
Bottom line: the carrier, the region and the server's subnet decide everything. For some users Outline runs for months, for others it dies within a day, and under heavy DPI — Russian mobile networks, Iran, China — plain Shadowsocks is unreliable.
Outline, VLESS Reality or AmneziaWG
- Outline — when simplicity and a quick start matter most: a one-command server, keys as links, per-key data limits without a separate panel. It suits a family or a small team, travel, countries with light filtering, and works well as a backup channel.
- VLESS + Reality — for heavily filtered networks such as mobile internet in Russia. The connection looks like TLS 1.3 to a real website; practitioners rate it among the most resilient options, although since late 2025 it has been blocked selectively in some Russian regions. See VLESS + Reality with 3x-ui.
- AmneziaWG — when you need a full WireGuard-class VPN over UDP with obfuscation: Your own AmneziaWG server.
The other options are compared in Protocols for your own VPN.
Step 1. Choose ports and open them
By default the install script picks random ports. Set them yourself so you know in advance what to open in the firewall:
- API port — any free port, for example
51443/tcp. Only the administrator needs it. - Key port — used by your users over TCP and UDP. If the server doesn't run a web server, start with
443: with a prefix, the port should match the protocol you're imitating.
If you have a domain, point an A record such as vpn.example.com at the server's IP. Then open the ports in ufw, SSH first, or enabling the firewall will cut your session. More in The ufw firewall: managing ports.
sudo ufw allow OpenSSH
sudo ufw allow 51443/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw enableStep 2. Install Outline Server
Run the official script. Without a domain, drop --hostname and the script detects the external IPv4 address itself. The script needs wget and curl; on a minimal image run sudo apt install -y wget curl first.
sudo bash -c "$(wget -qO- https://raw.githubusercontent.com/OutlineFoundation/outline-apps/master/server_manager/install_scripts/install_server.sh)" install_server.sh --hostname vpn.example.com --api-port 51443 --keys-port 443If Docker isn't installed, the script offers to install it from get.docker.com — answer Y. It then creates /opt/outline with a 128-bit secret API path and a self-signed certificate, starts shadowbox from the quay.io/outline/shadowbox:stable image along with watchtower, creates the first key and prints a line for the Manager:
CONGRATULATIONS! Your Outline server is up and running.
To manage your Outline server, please copy the following line (including curly
brackets) into Step 2 of the Outline Manager interface:
{"apiUrl":"https://vpn.example.com:51443/SECRET_PATH","certSha256":"FINGERPRINT"}If the firewall check printed BLOCKED, the ports are closed — go back to step 1. Check the containers:
sudo docker ps --format '{{.Names}}: {{.Status}}'Step 3. Connect Outline Manager
Download the Manager from getoutline.org or via the direct links in the official download list. Don't use builds from Telegram channels or file-sharing sites.
In the Manager, click + and choose Set up Outline anywhere. Skip the command in the instructions — the server is already installed. Paste the JSON line from step 2 into the Paste your installation output here field and click Done. The Manager checks the certificate fingerprint certSha256 and opens the server.
If you've lost the line, rebuild it from access.txt:
sudo awk -F: '/^certSha256:/{c=$2} /^apiUrl:/{sub(/^apiUrl:/,""); a=$0} END{printf "{\"apiUrl\":\"%s\",\"certSha256\":\"%s\"}\n", a, c}' /opt/outline/access.txtStep 4. Create and share keys
Creating and sharing
Click Add new key, rename it after the person and click the share icon. Copy access key gives you the ss://… string; Copy invitation gives a message with a link to install the client. Send keys over a secure channel: whoever has the string can connect.
Give each person their own key. One key works on all of that person's devices, but a key shared by a group can't be revoked for just one member.
Data limits
The server-wide limit is in the Settings tab under Data limits; a per-key limit is set with Data Limit in the key's menu. How it works:
- The window is a rolling 30 days, not a calendar month.
- Limits are checked once an hour, so a key can overshoot by up to an hour's worth of traffic.
- What counts is the server's outbound traffic: to the client and to websites.
- A key over its limit silently stops connecting until its 30-day usage drops below the threshold.
Revoking keys and changing the address
A removed key stops connecting. Hostname and Port for new access keys in Settings only affect keys handed out after the change; users keep their old links. That's why it pays to use a domain as the server address from the start, or to hand out dynamic keys.
Managing through the API
The Manager is optional: the same operations are API requests you can run on the server itself. That helps when your own network can't reach the server.
API_URL=$(sudo sed -n 's/^apiUrl://p' /opt/outline/access.txt)
curl -sk "$API_URL/server"
curl -sk "$API_URL/access-keys"
curl -sk -X POST -H 'Content-Type: application/json' -d '{"name":"anna","limit":{"bytes":50000000000}}' "$API_URL/access-keys"
curl -sk -X PUT -H 'Content-Type: application/json' -d '{"limit":{"bytes":100000000000}}' "$API_URL/access-keys/3/data-limit"
curl -sk -X DELETE "$API_URL/access-keys/3"In order: server settings including the version, the key list, a new key anna with a 50 GB limit, a 100 GB limit for the key with id 3, and deleting that key. The server-wide limit is a PUT to $API_URL/server/access-key-data-limit with the same body; everything else is in the api.yml spec.
Dynamic keys: ssconf://
A static ss:// key hard-codes the address, port and password. A dynamic key only holds the location of a config that the client downloads on every connection, so you can change the IP, port or prefix without reissuing keys.
- Take the key's
passwordandmethodfrom the output ofcurl -sk "$API_URL/access-keys". - Save the config to a file and publish it over HTTPS somewhere that doesn't depend on the VPN server's IP.
- Replace
https://withssconf://in the file's URL and add a name after#, for examplessconf://example.org/k/7f3a9c/anna.yaml#anna.
The client accepts three formats: an ss:// link (from version 1.8.1), JSON (from 1.8.0) and YAML (from 1.15.0). YAML lets you configure TCP and UDP separately:
transport:
$type: tcpudp
tcp:
$type: shadowsocks
endpoint: vpn.example.com:443
cipher: chacha20-ietf-poly1305
secret: KEY_PASSWORD
udp:
$type: shadowsocks
endpoint: vpn.example.com:443
cipher: chacha20-ietf-poly1305
secret: KEY_PASSWORDStep 5. Set a prefix disguise
A prefix is a few bytes the client puts at the start of the Shadowsocks salt. The connection then opens the way an allowed protocol does, and heuristics of the "let through anything that looks familiar" kind leave it alone. The server needs no changes: to it the prefix is just part of a random salt.
Requirements from the Outline documentation:
- Client version 1.9.0 or later.
- No longer than 16 bytes: a long prefix raises the risk of salt collisions, which weakens encryption and is detectable in itself.
- The key's port should match the protocol you're imitating.
TCP examples from the documentation, URL-encoded:
- HTTP request, port 80:
POST%20 - HTTP response, port 80:
HTTP%2F1.1%20 - TLS ClientHello, port 443:
%16%03%01%00%C2%A8%01%01 - TLS ServerHello, port 443:
%16%03%03%40%00%02 - SSH, port 22:
SSH-2.0%0D%0A
Append the prefix to a static key:
ss://[email protected]:443/?outline=1&prefix=%16%03%01%00%C2%A8%01%01In a static key the prefix applies to TCP only; for UDP, set prefix in the udp block of a YAML config. Bytes from 0x80 up are encoded as UTF-8 characters, so 0xA8 becomes %C2%A8. The safest way to get the string is encodeURIComponent() in a browser console.
Shadowsocks over WebSocket
If prefixes don't help, the next official step is Shadowsocks over WebSocket with TLS: from the outside it's ordinary HTTPS on port 443, it can sit behind a CDN, and it fits networks that allow only HTTP and HTTPS. You need client 1.15.0+ and a YAML key whose endpoint has the type websocket. The standard shadowbox can't do this: you need a separate outline-ss-server 1.9+ behind a web server, or Caddy with the outlinecaddy module, and Outline Manager can't manage such keys. Guides: WebSockets and Caddy. A CDN is no guarantee either: in 2025 Russia saw mass outages of services behind Cloudflare.
Step 6. Install the clients
Official Outline clients, current branch 1.21:
- Android — Google Play or the APK from the official storage; the APK version doesn't update itself.
- iOS and iPadOS — App Store, iOS 15.5 or later.
- macOS — Mac App Store, macOS 12.4 or later.
- Windows — installers for x64 and ARM64.
- Linux — deb packages for amd64 and arm64.
- ChromeOS — the Android app from Google Play.
Copy the key and open the client — it detects the key on the clipboard. Otherwise click Add server, paste the key, then Connect.
App stores in censoring countries remove VPN apps on government request: by April 2026 Apple had pulled hundreds of them from the Russian App Store, though as of 30 September 2026 Outline is still listed there. Keep the APK and the direct links handy. Third-party Shadowsocks clients with SIP002 support accept ss:// keys, but not all of them handle prefix or ssconf://.
Troubleshooting
The Manager can't see the server
- API port closed. Check
sudo ufw statusand try from your computer:curl -k https://vpn.example.com:51443/SECRET_PATH/servershould return JSON with the version. - Container not running. See
sudo docker ps -aandsudo docker logs --tail 50 shadowbox. - Server unreachable from your network. Manage keys through the API over SSH, or connect the Manager from another network.
- Server reinstalled. The secret and the certificate have changed: remove the server from the Manager and add it again with the new line.
A key won't connect
- Key port closed. Both TCP and UDP are needed:
sudo ss -tulpn | grep outline-ssshows what the server listens on,sudo ufw statusshows what's open. - Stale key. The key was deleted, the server reinstalled, or the key carries an old IP.
- Limit reached. Check the key's usage in the Manager.
- Old client. Prefixes need version 1.9.0, YAML keys 1.15.0.
- Blocked by the carrier. It works over Wi-Fi but not over mobile data — the server isn't the problem. From the same network, test the SSH port:
nc -vz vpn.example.com 22. If SSH connects but the key doesn't, the protocol is being filtered; if nothing connects, the IP is blocked.
Works, then stops after a while
- The protocol got detected. Try a prefix and another port; if that fails, move to WebSocket or VLESS Reality.
- The IP got blocked. You need a new IP; with a domain or dynamic keys users don't have to change anything.
- Limit reached. The key comes back by itself once 30-day usage falls below the threshold.
- Whitelist or allowlist regime. During shutdowns no server-side setting will help.
Slow speed, calls don't work
- Freeze after 15–20 KB. Pages start loading and hang while small requests get through. In Russia you can test the network with the TCP 16-20 checker.
- UDP blocked. The client tests UDP with a DNS query to
1.1.1.1and falls back to DNS over TCP without it, but calls, games and QUIC suffer. Open UDP on the key port. - Server bottleneck. Check CPU and bandwidth in
topand in the VPS panel. Shadowsocks has no "TCP over TCP" penalty: the client terminates connections locally and the server opens them afresh.
Is someone probing the server
outline-ss-server counts suspicious connections in the shadowsocks_tcp_probes metric. Prometheus listens on 127.0.0.1 only, usually on port 9090:
curl -sG http://127.0.0.1:9090/api/v1/query --data-urlencode 'query=sum by (status) (increase(shadowsocks_tcp_probes_count[24h]))'The ERR_REPLAY_CLIENT and ERR_REPLAY_SERVER statuses mean replayed handshakes — a typical sign of active probing. ERR_CIPHER is data that matched no key: probes, but also clients still using deleted keys.
Security
Basic server hardening is covered in How to harden SSH on your VPS and the VPS security checklist. The API only answers on its secret path, but the port itself is visible to any scanner. If you have a static IP, allow API access from it alone:
sudo ufw delete allow 51443/tcp
sudo ufw allow from 198.51.100.7 to any port 51443 proto tcpWithout a static IP you can close the API port entirely and reach it through an SSH tunnel: ssh -N -L 51443:127.0.0.1:51443 [email protected], with https://127.0.0.1:51443/SECRET_PATH in the Manager's JSON line. The Manager only checks the certificate fingerprint and doesn't care about the hostname.
The most sensitive files are /opt/outline/access.txt (full API access), persisted-state/start_container.sh (the secret path) and persisted-state/shadowbox_config.json (every key's password). Keep them out of publicly readable backups.
Anonymous usage statistics are not sent to the Outline developers by default. Other people's traffic will leave through your IP, and copyright complaints will come to you — see How to block torrents on your VPN or proxy.
Updates
watchtower checks the shadowbox image every hour and restarts the container itself. But when Docker Engine 29 came out in November 2025, the old containrrr/watchtower image broke with the error client version 1.25 is too old, and in January 2026 the install script switched to the maintained fork nickfedor/watchtower. If your server is older than that, check the log:
sudo docker logs --tail 5 watchtowerIf the error is there, recreate the container with the script's parameters:
sudo docker rm -f watchtower
sudo docker run -d --name watchtower --log-driver local --restart always -v /var/run/docker.sock:/var/run/docker.sock nickfedor/watchtower --cleanup --label-enable --tlsverify --interval 3600To update shadowbox by hand:
sudo docker pull quay.io/outline/shadowbox:stable
sudo /opt/outline/persisted-state/start_container.shIf there's no start_container.sh, the server was installed with an older version of the script; update it through watchtower.
Backup and migration
All state lives in /opt/outline: keys and limits in persisted-state/shadowbox_config.json, settings in shadowbox_server_config.json, the API certificate and secret in shadowbox-selfsigned.* and start_container.sh, statistics in prometheus/. For a consistent copy, stop the container while archiving:
sudo docker stop shadowbox
sudo tar -czpf /root/outline-backup.tar.gz -C /opt outline
sudo docker start shadowboxOn the new VPS, install Docker, copy the archive over, unpack it and start the container — keys, limits and Manager access carry over:
curl -fsSL https://get.docker.com | sudo sh
sudo tar -xzpf outline-backup.tar.gz -C /opt
sudo /opt/outline/persisted-state/start_container.shStart watchtower with the command from the Updates section. If the server used a domain, all that's left is to update the A record. If it used an IP, change the address in the Manager's JSON line and in the Hostname setting, and reissue keys to your users.
Sources
- OutlineFoundation/outline-server — the server, the API spec and a note on Shadowsocks resistance
- install_server.sh — the install script
- OutlineFoundation/tunnel-server —
outline-ss-serverand its probing defences - Outline developer documentation — prefixes, dynamic keys
- Issue #1684 — watchtower and Docker 29
- How the Great Firewall of China Detects and Blocks Fully Encrypted Traffic — USENIX Security 2023
- Iran's Stealth Internet Blackout: A New Model of Censorship — the June 2025 shutdown
- Iran's January 2026 Internet Shutdown — blackout and allowlist
- net4people/bbs #363 — Shadowsocks detection in Russia, May 2024
- net4people/bbs #490 — the 15–20 KB freeze, June 2025
- ntc.party: Shadowsocks not working (in Russian) — prefixes on Russian carriers, August 2024
- ntc.party: Shadowsocks / Outline blocking (in Russian) — July–August 2025
- Mediazona: Russia's internet censorship in 2026 — whitelists and VPN app removals