Skip to content
Stuff I'm Up To
Go back
Git and apt via SSH Reverse Tunnel

Git and apt via SSH Reverse Tunnel

Updated:

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:


Prerequisites

Host github.com
  HostName localhost
  Port 2222
  User git
  IdentityFile /root/.ssh/github_deploy_key
  StrictHostKeyChecking no

NOTE: The HostName localhost and Port 2222 entries redirect all github.com SSH 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:

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 (-D flag), but pip requires the pysocks package 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 requiredYes — one uvx pproxy process on your machineNo — built into the SSH tunnel
Works for pip out of the boxYesNo — still needs pysocks, same PEP 668 issue as -D
Works for apt out of the boxYesYes, if apt’s http/https transport was built with SOCKS support
Setup complexityOne uvx pproxy command + one extra -R forwardSingle -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>

Share this post:

Previous Post
Running Python 3.12 on Ubuntu 18.04 without touching apt
Next Post
The Day Claude Code Got Creative With Legacy Python