SJ cartoon avatar

Development ProxyJump'ing the Shark

Over the past few years, I've noticed an increasing trend: I'm spending less dev time in GUIs and more time in the terminal. Sure, GUIs have their moments, like when I'm working on VS Code or E2E testing a web app - but when it comes down to the nitty-gritty, the terminal is where the action is.

Living that Script Life

You wouldn’t believe the script-on-scripts I've got stacked like digital LEGO. And aliases? I've got more than you can shake a stick at. Leveraging Ansible for deployment and the tandem of docker/docker-compose for composition, the command line has become indispensable.

But, it's not just about the tools, it's about the integration. On my Mac, when a Linux environment is necessary for testing or development, I have various approaches. Sometimes it's a Docker/Podman container, other times I launch Parallels for specific environments, and occasionally, I spin up a Digital Ocean droplet. I SSH in, and off I go - or even better, I can run remote SSH commands and skip the persistent terminal altogether.


To make this a bit easier, let's define some terms that I'll be using:

  • Origin: your primary machine, likely the one you’re reading this post on right now
  • Remote: The endpoint machine that you want to SSH into
  • Proxy/Jump: The one or more machines that sit between the host and the remote

For example (this will make more sense after reading the next couple of sections). In my soon-to-be-mentioned workflows, the Origin is my Mac Mini, the Remote is sitting in my client's network, and the Proxy is my Windows or Fedora virtual machine in Parallels, running OpenVPN or Forticlient.

VPNs: The Cause of and Solution to

Here's where things can get dicey: client projects with VPNs. Don't get me wrong, VPNs are great for security, but they often come with their own ... quirks.

Each client has their VPN of choice and each VPN has their client of choice. In the past, I've had to install Forticlient. Forticlient's Mac and Linux clients are a load of garbage sauce... Additionally, they require administrator access, they run various "scans" on my machine, tuck that data away, and do all kinds of shady shit that I'm not onboard with.

In my ideal world, no third-party software would ever require administrator privileges but we seem to live in a world where developers want admin whenever they can get it under the guise of "it's more secure"... Yes, I'm talking about you 1Password.

VPNs are a different beast, so I get it. But, that doesn't mean I want some invasive piece of pseudo-malware on my computer.

To get around this, per client, I create one (or more) isolated Windows virtual machines in Parallels, where I install the VPN software (Forticlient or whatever else it may be). Now, that VM can access whatever guarded resources I need access to - typically this is an internal git repo or maybe an internal deployment server. But now, if I actually want to do some dev work, I have to re-copy/clone the repos off my Windows machine on to my origin Mac (or share folders, or mount folders, or whatever).

In another example, to infrequently access a remote client's server, I needed to connect to their VPN, and then SSH to their internal server, and rsync some files. They happened to use OpenVPN, but since I didn't want multiple VPN clients on my main machine (I use ProtonVPN), I created an isolated Fedora virtual machine, setup OpenVPN in there, and then I can rsync from Origin to the Fedora VM, and then rsync from the Fedora VM to the remote client server via OpenVPN.

I'm making this sound more complicated than it really is. In reality, with the scripts and tooling I've created, it works out to 1-2 extra commands for these workflows. But, that just doesn't sit right with me... It feels... Sloppy.

A Wild Game Changer Appears

During one of my SSH tunneling deep dives, I stumbled upon a magical option that I had never heard of before. ProxyJump - which is as literal as it sounds. It lets you SSH to a remote endpoint, via middle-men machines acting a proxies.

Holy crap, where have you been all my life?

Turns out it was only released a few years ago, but still, I’d never heard of it.

What Do I Do?

Well, with two additional SSH config options, you can route your SSH terminal through intermediaries to an endpoint. In my case, I’m using a single (virtual machine) intermediary, but there’s no reason you couldn’t add more jumps if that tickled your fancy.

From a practical perspective, you need to have your public SSH key on every intermediary server. There is a way around that, but I’m not covering it since I’ve never had that scenario

Here is the configuration you’ll need to setup in your SSH config, and it should then work all hunky dory:

Host my-local-proxy-virtual-machine
    HostName proxy-vm.local
    User proxyuser
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Host remote-server-i-want-to-access
    User remoteuser
    ProxyJump my-local-proxy-virtual-machine
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Now, when you want to SSH into the remote server, you can just run:

ssh remote-server-i-want-to-access

and you’ll be connected to the remote server via the proxy virtual machine.

Easy peasy.