Start your 3-day free trial
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.


A ShadowTLS v3 setup needs two separate credentials: an outer ShadowTLS user password and an inner proxy key. A TLS handshake alone does not prove that either layer has authorized a useful proxy request. This guide uses sing-box 1.14.3 on Ubuntu 24.04 LTS x64 at both ends, strict v3, and one Shadowsocks 2022 method.[1][2]
Key Takeaways
- Use v3
userson the server, not the v2 top-level password field.- Generate different secrets for the outer user and inner proxy.
- Use a TLS 1.3 handshake site you control and keep wildcard SNI disabled.
- Keep the inner listener private and verify actual application forwarding.
- Test wrong outer credentials, wrong inner keys, and handshake-site failures independently.
This procedure is based on the official sources listed below; a complete end-to-end test has not been performed. The version is a documentation reference, not an author-tested environment. Begin with the self-hosting ownership and recovery checklist.
The client path is application → local SOCKS listener → Shadowsocks encryption → ShadowTLS transport → server-side Shadowsocks → destination. The outer handshake uses your separate TLS site. That site's certificate name is not automatically the proxy server's address. Distinguish these responsibilities using the protocol and transport explanation.
Operate only authorized hosts. Require strict SSH host-key checking against a dedicated, independently verified known_hosts, console recovery, and backups of existing services and firewall policy. Stop on unknown, changed, or revoked host keys. Reserve unused TCP ports 8443 for ShadowTLS and loopback-only 24444 for the inner listener; check ownership with sudo ss -lntup before changing anything.
You need an independent HTTPS site you own, with TLS 1.3 and a publicly trusted certificate for handshake.example.com. Here its illustrative address is 203.0.113.20; the proxy server is 203.0.113.10. Replace both with real authorized addresses. Numeric addresses avoid a separate handshake-target DNS bootstrap in this example. Keep the site's certificate renewal and uptime under your control.
This two-layer service adds another availability dependency. If you do not need implementation-level control, compare VPS and managed VPN responsibilities. ShadowTLS also makes no guarantee of being undetectable; whether TLS fingerprinting can identify the tunnel remains a separate question from valid credentials.
Download the Linux amd64 archive from the official v1.14.3 release. Review its notes, security notices, and published verification material; unpack into a private staging directory. Install the extracted binary, root-owned and mode 0755, at /opt/sing-box-shadowtls/1.14.3/sing-box on each machine. Check the version with the full executable path. Do not silently substitute a development build.[1]
From the proxy server's network, check your own site:
openssl s_client -connect 203.0.113.20:443 -servername handshake.example.com -tls1_3 -verify_hostname handshake.example.com -verify_return_error </dev/null
Require TLS 1.3 negotiation and successful chain/name verification. A connection or readable HTTP page alone is insufficient. Fix the site's TLS configuration, certificate, clock, or reachability before continuing. The v3 protocol's strict-handshake assumptions do not justify relaxing verification.[3]
Create a non-login service account shadowtls-proxy. Create /etc/sing-box-shadowtls as root:shadowtls-proxy, mode 0750. Allow only TCP 8443 for intended clients in host/provider firewalls, and allow the server to reach your handshake site and authorized destinations. Keep SSH and recovery access intact; do not expose TCP 24444 to the network.
Run these locally in a private administrator terminal using the installed executable:
/opt/sing-box-shadowtls/1.14.3/sing-box generate rand --base64 32
/opt/sing-box-shadowtls/1.14.3/sing-box generate rand --base64 16
Use the first result as REPLACE_OUTER_PASSWORD. Use the second as REPLACE_INNER_KEY: 2022-blake3-aes-128-gcm requires a 16-byte key encoded as base64. Do not reuse the outer password or use an arbitrary memorable phrase for this inner method. Store both privately, with no screenshots or shared command history containing their values.[4]
With sudoedit, create /etc/sing-box-shadowtls/server.json. Replace the example IP and both secrets. This fixed v3 route uses users, strict_mode: true, and wildcard_sni: off. It does not forward arbitrary SNI names.[2]
{
"log": {"level": "warn"},
"dns": {
"servers": [{"type": "local", "tag": "system-dns"}]
},
"inbounds": [
{
"type": "shadowtls",
"tag": "st-in",
"listen": "0.0.0.0",
"listen_port": 8443,
"version": 3,
"users": [{"name": "trial-user", "password": "REPLACE_OUTER_PASSWORD"}],
"handshake": {"server": "203.0.113.20", "server_port": 443},
"strict_mode": true,
"wildcard_sni": "off",
"detour": "ss-in"
},
{
"type": "shadowsocks",
"tag": "ss-in",
"listen": "127.0.0.1",
"listen_port": 24444,
"network": "tcp",
"method": "2022-blake3-aes-128-gcm",
"password": "REPLACE_INNER_KEY"
}
],
"outbounds": [{"type": "direct", "tag": "direct", "domain_resolver": "system-dns"}],
"route": {"final": "direct"}
}
Inbound detour injects the authenticated outer stream into the named inner inbound. It is not an outbound route to the public internet. The inner listener is restricted to loopback, and only TCP is enabled. The server resolves application destination names through its local resolver; this does not promise privacy from that resolver.[4][5][6]
Set the configuration to root:shadowtls-proxy, mode 0640. Keep backups equally restricted. Neither the service user nor a client needs permission to replace the root-owned binary.
Create a user-owned mode-0700 directory on the Ubuntu client and save this as a mode-0600 client.json. Replace the proxy IP and both secrets, while retaining your handshake site's certificate name.
{
"log": {"level": "warn"},
"inbounds": [{
"type": "mixed",
"tag": "local",
"listen": "127.0.0.1",
"listen_port": 1082,
"set_system_proxy": false
}],
"outbounds": [
{
"type": "shadowsocks",
"tag": "ss-out",
"server": "127.0.0.1",
"server_port": 24444,
"method": "2022-blake3-aes-128-gcm",
"password": "REPLACE_INNER_KEY",
"network": "tcp",
"detour": "st-out"
},
{
"type": "shadowtls",
"tag": "st-out",
"server": "203.0.113.10",
"server_port": 8443,
"version": 3,
"password": "REPLACE_OUTER_PASSWORD",
"tls": {
"enabled": true,
"server_name": "handshake.example.com",
"min_version": "1.3",
"insecure": false
}
}
],
"route": {"final": "ss-out"}
}
The Shadowsocks outbound supplies inner encryption. Its detour selects st-out; the pinned ShadowTLS outbound connects to its own configured server and ignores the dial destination supplied by that upstream layer. Thus 127.0.0.1:24444 here is not a request to reach a client-side server. Do not remove the detour or replace the final outbound with st-out, which would skip inner encryption.[7][8]
Check both configurations before startup:
sudo -u shadowtls-proxy /opt/sing-box-shadowtls/1.14.3/sing-box check -c /etc/sing-box-shadowtls/server.json
/opt/sing-box-shadowtls/1.14.3/sing-box check -c ./client.json
Require successful exits. These checks validate configuration, not the entire forwarding path. Fix a placeholder, malformed key, unsupported field, or unreadable file rather than bypassing validation.
Create the root-owned /etc/systemd/system/sing-box-shadowtls.service:
[Unit]
Description=ShadowTLS v3 and inner Shadowsocks
After=network-online.target
Wants=network-online.target
[Service]
User=shadowtls-proxy
Group=shadowtls-proxy
ExecStart=/opt/sing-box-shadowtls/1.14.3/sing-box run -c /etc/sing-box-shadowtls/server.json
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
UMask=0077
[Install]
WantedBy=multi-user.target
Run sudo systemctl daemon-reload, then sudo systemctl start sing-box-shadowtls. Confirm this service owns TCP 8443 and only loopback TCP 24444. On the client, run the pinned executable with run -c ./client.json in the foreground. Confirm its mixed listener is only 127.0.0.1:1082; do not enable system-wide proxying or TUN for this trial.
Use a controlled HTTPS endpoint with known content:
curl --fail --show-error --noproxy '' --proxy socks5h://127.0.0.1:1082 https://check.example.net/health
Replace the URL with your own authorized endpoint. Require the expected application response, then repeat with a small controlled upload and download. A successful TLS handshake, active service, or connected local socket is not the acceptance result. Configure one test browser for the local SOCKS listener and open the IP address tool. Its result covers that request only, not all DNS, IPv6, UDP, or applications.
Make private copies of the client configuration and change exactly one condition per trial. Stop the previous foreground instance before starting a copy on the same local port. Restore the valid file and repeat the positive request between negative cases.
| Check method | Expected behavior | Failure handling |
|---|---|---|
| Correct outer password and inner key | Controlled HTTPS request forwards successfully | Separate local listener, handshake target, outer authentication, inner authentication, and destination |
| Replace only outer password with another generated value | No usable authenticated proxy request | A possible TLS handshake is not proof of authorized forwarding |
| Replace only inner key with another valid 16-byte base64 key | Inner proxy authentication fails | Do not use a malformed key: that tests parsing instead of authentication |
| Use a separate trial config with an unreachable handshake target | Trial forwarding cannot complete | Restore target; do not change unrelated production networking |
| Point a separate trial at your TLS-1.2-only test site | Strict v3 route cannot satisfy its TLS 1.3 condition | Restore the approved TLS 1.3 site, not weaker client verification |
| Restart only this service and send a fresh request | Authorized forwarding recovers | Inspect file permissions, target availability, and startup ownership |
For target-failure cases, use a separate disposable trial instance and unused port, or a scheduled isolated test window. Do not turn off a shared website to create a failure. Keep logs restricted and redact credentials, private addresses, and destination metadata before sharing an error report.
Before upgrading, privately preserve the binary, server/client files, unit, and handshake-site configuration. Follow sing-box release and migration notes; stage another version directory, check both files, and repeat the layered matrix. Follow the handshake site's certificate renewal too: an unchanged proxy binary can fail when its external dependency expires.
For revocation, remove the named outer user from the server configuration and validate it. Restart this specific service to load the change and terminate existing connections; this interrupts other users of the same instance. Verify that a fresh connection with the removed password fails. The shared inner key is a different boundary: rotating it requires updating every authorized client that shares it. Retire both secrets when decommissioning the trial.
Rollback restores a compatible saved configuration and executable path for this unit, then repeats the positive and refusal checks. Do not keep a vulnerable release indefinitely. To shut down, restore the application's original proxy setting, stop the client, run sudo systemctl disable --now sing-box-shadowtls, and remove only its TCP firewall rule. Verify both listeners disappear. Keep the independently managed handshake site running if anything else uses it; deletion of this proxy does not authorize deleting that site.
Treat the outer user, inner key, handshake site, and destination forwarding as separate checks. Correct configuration is necessary, but acceptance also requires useful forwarding, deliberate refusal, restart, and revocation. No end-to-end deployment results are claimed here.
No. Generate independent values for the two layers. This Shadowsocks 2022 method requires a base64-encoded 16-byte key, while the outer password serves ShadowTLS authentication.
Use the v3 users array in this configuration. Mixing version-specific fields obscures which authentication boundary you actually configured and makes troubleshooting less reliable.
No. Check a controlled destination through the complete inner and outer chain, then verify that incorrect credentials cannot obtain that same useful forwarding.
Ownership lets you maintain its certificate and TLS policy, authorize the traffic, and plan recovery without making an unrelated public site your operational dependency.
Keep it off for this route. The procedure uses one approved handshake target and does not authorize arbitrary server-name forwarding or a broader destination matrix.
The outer inbound injects streams into it inside the server process. A public inner listener would create another reachable authentication surface outside this bounded setup.
No. They are independent secrets. Remove the outer user and terminate its sessions; rotate a shared inner key separately and update all affected authorized clients.
Sources:
Sources checked 11 October 2026.
Related Articles:
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.