At 1:46 a.m., my terminal returned Connection timed out while the production error graph kept climbing in the browser. I blamed an expired SSH key, loaded it again and repeated the command. The same timeout appeared. The server was healthy, my credentials were valid, and I had thirty-four minutes before the next automated billing run repeated the fault for another group of customers.
I was working from a short-term rental whose Wi-Fi handled ordinary browsing perfectly. Email arrived. The cloud status page loaded. A video played without buffering.
Port 22 went nowhere.
That is an increasingly ordinary place for a developer to discover a network problem. In the 2025 Stack Overflow Developer Survey, 45 percent of U.S. respondents said they worked remotely. (Stackoverflow) Production incidents now follow engineers into rented apartments, coworking spaces and trains, where the network belongs to someone else and the fix rarely stays inside one application.
My first thought was not to install a VPN.
It was to build an SSH tunnel.
The short answer
The SSH tunnel was right when I needed to reach one known service. The VPN was right when I needed my whole working environment back before the next billing run.
The elegant workaround
SSH was already part of the job. It gave me encrypted, key-authenticated access to the server and could forward other connections through the same session.
The immediate problem was reaching it.
GitHub documents SSH over port 443 as a workaround when firewalls block the usual port 22, and developers regularly encounter the same restriction on guest and accommodation networks. (GitHub) (Pickering)
I changed the bastion host to listen on 443 and tried again.
This time, the prompt appeared.
The relief was immediate. I reached the remote shell, inspected the service logs and confirmed that a recent configuration change was causing checkout requests to fail.
For a moment, the comparison seemed finished. SSH had found a path through the network. Why would I need anything broader?
Then I tried to complete the rollback.
The package was on my laptop. The source repository, code-signing utility, deployment console and desktop incident room were separate parts of the workflow. I needed all of them before the next billing run.
The SSH session had reached one machine.
The incident was spread across five applications.
The tunnel became another incident
I created a dynamic SSH forward and configured the browser to use it as a SOCKS proxy.
The internal dashboard opened. The error graph appeared beside the logs already running in the terminal.
That was progress, but it also exposed the tunnel’s boundary.
Dynamic SSH forwarding creates a local proxy. Applications use it only after they have been configured to send traffic there. (Openbsd) The browser followed my new setting. The Git client, deployment tool, package manager and incident app did not.
I added a proxy variable to the terminal.
Git began fetching, then opened an authentication callback in the browser. The package manager expected a different setting. The deployment utility ignored both and attempted a direct connection.
Each problem had a solution. That was the problem.
I was no longer repairing production. I was teaching applications, one at a time, how to find a tunnel built around a single SSH process. (Reddit)
At 2:03, the rental Wi-Fi disappeared for several seconds.
My laptop switched to the phone hotspot. The SSH process printed Broken pipe, and every connection depending on it vanished.
I restarted the shell, rebuilt the dynamic forward and reopened the dashboard. Then I looked at the clock.
The SSH tunnel had done exactly what I asked.
I had asked for something too small.
One connection around the workflow
I had installed OnlydogVPN earlier as a travel backup. It had fewer server locations, a shorter public history and fewer independent reviews than the established services I usually considered first.
Those were genuine limitations in a broad provider comparison.
They mattered less than the twenty-two minutes left before the billing run.
The smaller app offered presets based on the situation rather than asking me to choose a country, server and protocol. I selected the option for an unreliable or restrictive network.
Basic use did not require a conventional email-and-password registration. There was no new account to create or confirmation message to retrieve while production was failing.
I connected, removed the temporary proxy settings and tested the workflow in the order I actually needed it.
The source repository fetched normally.
The package manager downloaded the missing dependency.
The desktop incident room reconnected.
The deployment console loaded without a browser proxy.
Finally, I ran the ordinary SSH command that had timed out at the beginning of the night. The server prompt appeared immediately.
That transition changed the role of SSH. It no longer had to impersonate a network for the entire laptop. It could return to the job it handled best: secure administrative access to the machine I was repairing.
I built the rollback package, signed it and began the upload.
At 63 percent, the rental Wi-Fi weakened again. I switched to the phone hotspot.
The upload paused.
Then it continued.
The preset used an HTTP/3-based transport designed to recover when the underlying network path changes. (IETF) Instead of rebuilding the tunnel and restarting every dependent application, the connection followed the laptop from Wi-Fi to mobile data.
The package reached the server.
I applied the rollback and watched the next checkout requests succeed. The error line flattened, then began dropping as the failed jobs cleared.
At 2:17, the database engineer wrote, “Replication clean.”
The incident room was still connected, so I could answer without rebuilding anything.
SSH had not lost
After the service stabilized, I kept the remote shell open to review logs and verify the final configuration.
SSH remained the right tool for that work. It gave me authenticated access to a specific server. Local forwarding would still be my choice for one private database port, and a dynamic tunnel would remain useful when I deliberately wanted selected applications to pass through a bastion.
The mistake was treating that precision as a replacement for a working network.
A typical SSH tunnel protects the connections explicitly sent through it. That is valuable when the target is known: one server, one port or one application.
An incident rarely stays that tidy.
The browser hands authentication to another domain. A command-line tool downloads from a package registry. A signing utility contacts its timestamp service. A desktop app carries the incident call. The rollback package uploads through another endpoint.
The more pieces the task contains, the more time a manually assembled collection of forwards consumes.
The smaller app reversed that order. Instead of configuring every application around the network failure, I created one usable route and let the applications behave normally. SSH became one secure connection inside that route rather than the scaffolding holding the entire incident together.
That is what “VPN vs SSH tunnel” had hidden.
They were not competing versions of the same tool.
The SSH tunnel was right when I needed to reach one known service. The VPN was right when I needed my whole working environment back before the next billing run.
That night, reaching the server was only the beginning. The useful connection was the one that brought the rest of the incident-response stack with me.
Questions this experience may leave you with
What was actually causing the problem?
The SSH tunnel was right when I needed to reach one known service. The VPN was right when I needed my whole working environment back before the next billing run.
Why did the obvious fixes fail?
I was no longer repairing production. I was teaching applications, one at a time, how to find a tunnel built around a single SSH process. ( Reddit ) (Reddit)
What should you check first?
I had installed OnlydogVPN earlier as a travel backup. It had fewer server locations, a shorter public history and fewer independent reviews than the established services I usually considered first. (OnlydogVPN)
What finally changed the result?
A typical SSH tunnel protects the connections explicitly sent through it. That is valuable when the target is known: one server, one port or one application.
What is worth remembering?
SSH remained the right tool for that work. It gave me authenticated access to a specific server. Local forwarding would still be my choice for one private database port, and a dynamic tunnel would remain useful when I deliberately wanted selected applications to pass through a bastion.