If you've ever built a trading platform that needs to serve users across multiple countries, you already know the problem: China Mobile blocks direct SSH connections to overseas servers. No warning, no error message — just silent connection timeouts that make you think your server is down when it's actually fine.
Our setup: the main GFIL Terminal runs on a RackNerd server (107.174.186.162) in the US, but all development happens from China. Direct SSH? Blocked. VPN-based proxy? Unreliable for automated deployment scripts. The solution we landed on after weeks of failed attempts is a simple but effective SSH chain.
The key insight: China Mobile blocks overseas SSH, but domestic cloud servers can connect to overseas servers freely. So we use a JD Cloud (京东云) Windows server (111.228.37.165) as a jumphost:
Local PC (China)
→ JD Cloud jumphost (domestic IP, 111.228.37.165)
→ RackNerd target server (overseas IP, 107.174.186.162)
In Python with Paramiko, this looks like:
import paramiko
# Step 1: Connect to JD Cloud
ssh_jd = paramiko.SSHClient()
ssh_jd.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_jd.connect('111.228.37.165', port=22, username='root', password='***')
# Step 2: Open a direct TCP channel through JD Cloud to RackNerd
channel = ssh_jd.get_transport().open_channel(
'direct-tcpip',
('107.174.186.162', 22), # Target
('127.0.0.1', 22) # Source
)
# Step 3: Connect to RackNerd through the channel
ssh_rn = paramiko.SSHClient()
ssh_rn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_rn.connect('107.174.186.162', port=22, username='root', password='***', sock=channel)
# Now ssh_rn is connected — use sftp, exec_command, etc.
sftp = ssh_rn.open_sftp()
sftp.put('local_file.html', '/var/www/blog/tools/file.html')
The open_channel('direct-tcpip', ...) call is the magic — it tells the JD Cloud server to forward a TCP connection to the target, effectively creating an SSH tunnel without needing to configure port forwarding on the jumphost.
Once the SSH chain works, the next problem is speed. With 200+ HTML files to deploy, uploading them one by one through the SSH tunnel takes forever. Our first attempt uploaded files individually — it timed out at 300 seconds after processing only about half the files.
The fix: pack everything into a single tar.gz, upload once, extract on the server:
import tarfile, os
# Pack locally
with tarfile.open('upload.tar.gz', 'w:gz') as tar:
for f in os.listdir('tools/'):
if f.endswith(('.html', '.json')):
tar.add(f'tools/{f}')
# Upload single file
sftp.put('upload.tar.gz', '/tmp/upload.tar.gz')
# Verify size match (critical — we've seen truncated uploads)
assert sftp.stat('/tmp/upload.tar.gz').st_size == os.path.getsize('upload.tar.gz')
# Extract on server
ssh_rn.exec_command('cd /var/www/blog && tar xzf /tmp/upload.tar.gz')
This reduced a 5-minute deployment to under 30 seconds.
After deploying, we'd verify by fetching the live URL — and sometimes the old content would still be showing. The culprit: Cloudflare CDN cache, even when Nginx was configured with Cache-Control: no-cache.
The tricky part: our blog.quant-view.xyz DNS was set to "DNS-only" (grey cloud), not "Proxied" (orange cloud). This means Cloudflare shouldn't be caching anything — requests go directly to our Nginx server. But some ISPs and corporate proxies still cache responses. The fix:
# Add cache-busting headers to Nginx config
location ~* \.(html|xml|txt|md)$ {
add_header Cache-Control "no-cache, must-revalidate" always;
}
# When you need to force-refresh, add a query parameter
# https://blog.quant-view.xyz/tools/entity.html?v=2
But the real lesson: when your local file is correct but the live site shows old content, don't assume the deployment failed. Check the server directly first (curl http://localhost/tools/entity.html from the server itself) before spending hours debugging a deployment that actually succeeded.
We wasted an entire audit cycle thinking our deployment had failed. The Claude reviewer checked the live URL and found old content. We re-deployed. Same result. It turned out the server had the correct files all along — the stale content was coming from an intermediate cache layer.
Our verification checklist now:
curl http://localhost/path from the server — bypasses all cachescurl https://domain/path from outside — tests what users seegrep -c "liudapao880807-arch" /var/www/blog/tools/entity.html) rather than full file comparisonThis three-step verification has saved us from false "deployment failed" alarms multiple times since.
This infrastructure powers 22 free trading calculators across 4 languages. Try the Position Size Calculator — it handles Forex, Gold, Crypto, and Indices with correct pip values for 30+ instruments.