Git and apt Access via SSH Reverse Tunnel
Background
Some customer sites block outbound SSH (port 22) at the network level. This prevents git from cloning or pushing to GitHub using SSH, even if the deploy key is correctly configured on the remote machine. In some cases outbound HTTP/HTTPS may also be restricted, breaking apt and pip package installation.
NOTE: This applies to any customer systems that sit within a firewall like this — it’s not specific to one particular setup.
The solution is to create a reverse SSH tunnel from your local machine to the customer machine, forwarding ports through to the required services. Traffic on the customer machine then connects via localhost instead of directly to the internet, bypassing the firewall restriction.
This works because:
- You already have an SSH connection into the customer machine (inbound SSH is allowed)
- Your local machine can reach GitHub and the internet without restriction
- The tunnel piggybacks traffic through your existing SSH session
Prerequisites
- An active SSH connection (or control socket) to the customer machine
- Your local machine can reach
github.comon port 22 uvinstalled on your local machine (for the HTTP proxy) —uvxships with it, nothing else to install- A deploy key configured on the customer machine at
/root/.ssh/github_deploy_key - The customer machine’s
/root/.ssh/configcontains:
Host github.com
HostName localhost
Port 2222
User git
IdentityFile /root/.ssh/github_deploy_key
StrictHostKeyChecking no
NOTE: The
HostName localhostandPort 2222entries redirect allgithub.comSSH traffic through the tunnel. Without these, you would need to specify the port manually every time.
Setting Up the HTTP Proxy
As well as SSH access, you’ll likely need to install packages via apt or pip on the customer machine. These tools expect an HTTP proxy rather than a SOCKS proxy — and Python’s pip in particular requires an extra dependency (pysocks) to use SOCKS, which creates a chicken-and-egg problem on modern systems (PEP 668 prevents pip installing system-wide packages directly).
The cleanest solution is to run a lightweight HTTP proxy with pproxy via uvx, so there’s no Docker daemon and nothing to install on your machine:
uvx pproxy -l http://127.0.0.1:8888 &
This starts pproxy listening on port 8888 of your local machine as a plain HTTP proxy, with no configuration file needed. uvx fetches it into an ephemeral environment on first run, so there’s nothing left behind afterwards. Stop it when you’re done:
kill %1
(or bring it to the foreground with fg and hit Ctrl+C, if you didn’t background it with &).
Opening the Tunnels
From your local machine, run:
ssh -R 2222:github.com:22 -R 8888:localhost:8888 <customer-system>
What this does:
-R 2222:github.com:22— forwards port2222on the customer machine through your local machine togithub.com:22for git/SSH access-R 8888:localhost:8888— forwards port8888on the customer machine back to the pproxy HTTP proxy running on your local machine, giving the customer machine outbound HTTP/HTTPS access
Leave this terminal open for the duration of your work. The tunnels close when you exit or disconnect.
Using Git on the Customer Machine
Once the tunnel is open, SSH to the customer machine and use git normally:
git clone [email protected]:YourOrg/your-repo.git
git pull
git push
Because /root/.ssh/config redirects github.com to localhost:2222, all git SSH traffic will flow through your tunnel transparently.
Using apt on the Customer Machine
apt-get -o Acquire::http::Proxy="http://localhost:8888" update
apt-get -o Acquire::http::Proxy="http://localhost:8888" install <package>
Or set it persistently for the session:
export http_proxy=http://localhost:8888
export https_proxy=http://localhost:8888
apt-get update && apt-get install <package>
Using pip on the Customer Machine
Add the proxy to the venv’s pip.conf so it’s used automatically:
cat > /path/to/venv/pip.conf << EOF
[global]
proxy = http://localhost:8888
EOF
Or pass it per-command:
pip install --proxy http://localhost:8888 <package>
Why not SOCKS? SSH tunnels create a SOCKS proxy by default (
-Dflag), but pip requires thepysockspackage to use SOCKS. On modern systems (PEP 668), pip cannot install packages system-wide, creating a catch-22. Using an HTTP proxy via pproxy avoids this entirely.
Alternative: Reverse SOCKS Proxy for apt (no extra process at all)
Instead of running pproxy, apt can be pointed straight at a reverse dynamic (SOCKS) forward created by SSH itself, with no extra proxy process required on either end.
Opening the reverse SOCKS tunnel
From your local machine:
ssh -R 1080 <customer-system>
-R 1080 (with no destination host/port) tells SSH to open a reverse dynamic forward: it starts a SOCKS proxy on localhost:1080 on the customer machine, which routes connections back through the tunnel and out via your local machine’s network — the same mechanism as a normal -D dynamic forward, just running in the opposite direction.
Configuring apt to use it
apt’s libcurl-based http/https transport can talk to a SOCKS proxy directly via the socks5h:// scheme, so no bridging tool (privoxy, polipo, etc.) is needed. Add this to /etc/apt/apt.conf.d/90socks-proxy on the customer machine:
Acquire::http::Proxy "socks5h://localhost:1080/";
Acquire::https::Proxy "socks5h://localhost:1080/";
socks5h (as opposed to plain socks5) resolves DNS hostnames through the proxy rather than locally on the customer machine — important here, since the customer machine’s own DNS/firewall may not be able to resolve or reach the package mirrors directly.
Then use apt as normal:
apt-get update
apt-get install <package>
Comparison with the pproxy approach
| pproxy (HTTP proxy) | reverse SOCKS (ssh -R 1080) | |
|---|---|---|
| Extra process required | Yes — one uvx pproxy process on your machine | No — built into the SSH tunnel |
| Works for pip out of the box | Yes | No — still needs pysocks, same PEP 668 issue as -D |
| Works for apt out of the box | Yes | Yes, if apt’s http/https transport was built with SOCKS support |
| Setup complexity | One uvx pproxy command + one extra -R forward | Single -R 1080 flag, one apt.conf.d file |
In practice: use the reverse SOCKS proxy when you only need apt and want zero extra processes running anywhere; fall back to pproxy when you also need pip, or if the customer machine’s apt isn’t built with SOCKS support (older Debian/Ubuntu images sometimes aren’t).
Verifying the Tunnels are Working
Git/SSH:
ssh -T [email protected]
Expected response:
Hi YourOrg! You've successfully authenticated, but GitHub does not provide shell access.
HTTP proxy (pproxy):
curl -x http://localhost:8888 https://pypi.org
Should return an HTTP 200 response.
Reverse SOCKS proxy:
curl -x socks5h://localhost:1080 https://pypi.org
Should return an HTTP 200 response.
Closing the Tunnels
Exit or Ctrl+C the terminal running the ssh -R command on your local machine. Then stop pproxy (if you backgrounded it with &):
kill %1
Troubleshooting
bind: Address already in use — Port 2222, 8888, or 1080 is already in use on the customer machine. Try different ports and update /root/.ssh/config, pip.conf, or apt.conf.d to match.
Connection refused on the customer machine — The tunnel isn’t running. Go back to your local machine and run the ssh -R command.
kex_exchange_identification: read: Connection reset by peer — Git is trying to connect directly to GitHub rather than through the tunnel. Check that /root/.ssh/config is configured correctly with HostName localhost and Port 2222.
pip SOCKS error (Missing dependencies for SOCKS support) — pip is trying to use a SOCKS proxy. Make sure you’re using the HTTP proxy (http://localhost:8888) rather than a SOCKS address, and that pip.conf is configured correctly inside the venv.
apt SOCKS error / falls back to direct connection — apt’s transport wasn’t built with SOCKS support. Fall back to the pproxy HTTP proxy approach above instead.
Tunnel drops during long operations — Add keepalive options to your local SSH command:
ssh -R 2222:github.com:22 -R 8888:localhost:8888 -R 1080 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 <customer-system>