Establishing a secure SSH tunnel through three Ubuntu machines allows you to bypass restrictive firewalls by chaining connections. This method routes traffic through intermediate hosts that have open ports, effectively masking the final destination from the initial firewall rules. By configuring each node correctly, you can access internal services securely without exposing them directly to the public internet.
Understanding the Network Topology
Before configuring any software, it is crucial to map out the roles of your three Ubuntu machines. In this scenario, Machine A is your local client, Machine B acts as the first hop (the entry point that has direct internet access), and Machine C is the target server located behind the restrictive firewall. The goal is to connect from A to B, and then from B to C, creating a continuous encrypted path. This approach relies on the fact that while the firewall blocks direct inbound connections to C, it likely allows outbound SSH traffic or permits connections from B to C if they are on the same private network segment. You must ensure that all three machines run compatible versions of OpenSSH and that key-based authentication is preferred over passwords for automation and security. For more details on securing these nodes, refer to our guide on
Securing an Ubuntu Server for Development Purposes.
Configuring Key-Based Authentication
Security is paramount when chaining SSH connections. Using password authentication introduces significant risk and complexity, especially across multiple hops. Start by generating SSH keys on your local machine, Machine A. Run the command
ssh-keygen -t ed25519 to create a strong key pair. Next, copy the public key to Machine B using
ssh-copy-id user@B_IP. Once authenticated to B, generate another key pair specifically for the connection to C, or reuse the existing one if permissions allow. Copy the public key from B to C using
ssh-copy-id user@C_IP. This step ensures that both Machine B and Machine C trust the respective identities without requiring manual password entry during the tunnel setup. If you encounter permission issues during this process, check the ownership of the
.ssh directory and ensure it is set to 700.
Setting Up the SSH Config File
The most efficient way to manage multi-hop SSH connections is through the configuration file on Machine A. Edit or create the file
~/.ssh/config and define a new host entry for Machine C. Within this block, use the
ProxyJump directive to specify Machine B as the intermediary. The configuration should look like this:
Host c_server
HostName C_IP_Address
User ubuntu
ProxyJump B_IP_Address
IdentityFile ~/.ssh/id_ed25519
This single configuration line tells SSH to automatically establish a connection to B before attempting to reach C. It handles the routing transparently, meaning you do not need to manually open two terminal windows. When you later execute
ssh c_server, the system will seamlessly bridge the gap between A and C via B. Ensure that the
HostName field matches the actual IP address or DNS name reachable from Machine B. If Machine C is only accessible via a private IP from B, use that private IP here rather than a public one.
Testing and Verifying the Connection
After saving the configuration, test the connectivity from Machine A. Run
ssh c_server and observe the output. If configured correctly, you should be logged into Machine C without being prompted for a password for either B or C. To verify that the traffic is indeed passing through B, you can check the active connections on Machine B using
ss -tnp | grep ssh. You should see established connections originating from A destined for C. Additionally, enable verbose mode with
ssh -v c_server to see the detailed handshake process. This helps identify where the connection might fail if there are network restrictions. For deeper insights into troubleshooting packet drops that might interfere with this chain, see
Troubleshooting Dropping Packets in iptables.
Maintaining Persistent Tunnels
For long-term usage, consider setting up persistent tunnels using tools like
autossh or systemd timers. This prevents disconnections due to temporary network glitches. Configure autossh to monitor the connection to C and restart it if it fails. This is particularly useful for accessing development environments or databases that require constant availability. Remember to update your SSH config if IP addresses change, and regularly rotate your SSH keys to maintain high security standards. Always restrict the allowed commands in the authorized_keys file on Machine C if the connection is used for automated tasks, adding an extra layer of protection against unauthorized access.
Frequently Asked Questions
What happens if Machine B goes offline?
If Machine B becomes unreachable, the SSH connection from A to C will fail immediately because B is the required gateway. You must restore connectivity to B before re-establishing the tunnel. Consider setting up a backup proxy server if high availability is critical for your workflow.
Can I use port forwarding instead of ProxyJump?
Yes, you can use LocalForward directives in your SSH config, but ProxyJump is generally simpler and more secure for chaining hosts. Port forwarding requires managing multiple local ports and can be more complex to configure correctly. ProxyJump handles the routing internally without exposing additional ports on your local machine.
Is it safe to share SSH keys across multiple machines?
Sharing the same key pair across machines reduces security if one machine is compromised. It is best practice to generate unique key pairs for each hop or role. This limits the blast radius of a potential breach and makes auditing easier. Use different identity files for connections to B and C if possible.
How do I troubleshoot connection timeouts?
Check the firewall rules on both Machine B and Machine C to ensure SSH traffic is permitted. Use the verbose flag (-v) to pinpoint where the connection hangs. Verify that the network interfaces on all machines are properly configured and that routing tables allow traffic between the subnets.
Can I access internal services on Machine C via the tunnel?
Absolutely. Once connected, you can forward local ports to services running on C using additional LocalForward rules in your SSH config. This allows you to access web servers, databases, or other applications on C as if they were running locally on A. Just ensure the services on C are bound to localhost or the correct interface.