Reverse proxy to api.anthropic.com, enabling Claude Code usage from environments where Anthropic API endpoints are blocked.
Client (Claude Code)
→ HTTPS → api.davidata.com:443 (server, nginx)
→ nginx stream (SNI routing) → 127.0.0.1:4444
→ nginx http (TLS termination, Let's Encrypt cert)
→ proxy_pass https://api.anthropic.com (re-encrypted)
The proxy terminates TLS on the server using a Let's Encrypt certificate, then re-encrypts to Anthropic. The server sees traffic in the clear between termination and re-encryption, but this is acceptable since it is our own server.
| Component | Location |
| nginx stream SNI map | /etc/nginx/nginx.conf (stream block) |
| nginx http site config | /etc/nginx/sites-enabled/api.davidata.com |
| TLS certificate | Let's Encrypt via certbot + certbot-dns-he-ddns plugin (DNS-01) |
| Cert credentials | /etc/letsencrypt/he-credentials/api.davidata.com.ini (he.net DDNS password) |
| Public DNS | A record at he.net: api.davidata.com → 37.123.171.156 |
| Local DNS | Router /etc/hosts: api.davidata.com → 10.0.20.9 |
Claude Code uses Server-Sent Events for streaming responses. The nginx config includes:
Set the base URL environment variable:
export ANTHROPIC_BASE_URL=https://api.davidata.com
WSL2 note: WSL2 uses different DNS than Windows. Add a manual /etc/hosts entry:
10.0.20.9 api.davidata.com
Certbot handles auto-renewal via its systemd timer. The certbot-dns-he-ddns plugin uses he.net's DDNS API to set TXT records for DNS-01 challenges.
To test renewal:
sudo certbot renew --cert-name api.davidata.com --dry-run
Context: the proxy was originally built to bypass category-based blocking of api.anthropic.com on a corporate laptop (Scientific Games Corp). A separate question is whether the corporate inspection apparatus (Netskope) is also decrypting and reading the payload as it passes through the laptop.
Compare the TLS certificate served to the client against the true Let's Encrypt certificate served by nginx. If they differ, MITM is occurring.
From the server (ground truth):
echo | openssl s_client -servername api.davidata.com -connect 37.123.171.156:443 2>/dev/null \
| openssl x509 -noout -issuer -subject -fingerprint -sha256
Expected: Issuer = Let's Encrypt, CN=E7 (or current LE intermediate).
From the work laptop — WSL shell:
echo | openssl s_client -servername api.davidata.com -connect api.davidata.com:443 2>/dev/null \
| openssl x509 -noout -issuer -subject -fingerprint -sha256
From the work laptop — native Windows (PowerShell):
$req = [Net.HttpWebRequest]::Create("https://api.davidata.com/")
$req.Method = "HEAD"
try { $req.GetResponse() | Out-Null } catch {}
$cert = $req.ServicePoint.Certificate
"Issuer: " + $cert.Issuer
"Subject: " + $cert.Subject
"Thumb: " + $cert.GetCertHashString("SHA256")
| Path | Issuer seen | Verdict |
| WSL → api.davidata.com | Let's Encrypt (genuine) | Uninspected |
| WSL → chat.openai.com | Google Trust Services (genuine) | Uninspected |
| PowerShell → api.davidata.com | Scientific Games Corp | MITM |
Interpretation: the endpoint inspection agent sits in the Windows network stack and does not reach into the WSL2 Hyper-V NIC. Windows-native processes (browsers, native node.exe, PowerShell, VS Code on Windows) have HTTPS decrypted and readable. WSL2 processes do not.
Adding a second tunnel (SSH, WireGuard, OpenVPN, DoH to a personal resolver, custom TLS-on-443) to hide the proxy further is counter-productive. Modern SWG tools classify unknown encrypted traffic as potential tunnel and flag it loudly. The current arrangement — standard HTTPS to an uncategorized .com — is the quietest available configuration precisely because it looks ordinary. A louder tunnel would lower the floor of suspicion without raising the ceiling of visibility.
Do not use canary DLP triggers (fake credit card numbers, etc.) to probe inspection depth. Doing so will light a torch on a SOC dashboard with your name beneath it. The cost of confirming what is already suspected is an awkward conversation with the employer.