SSH, aka Secure Shell Protocol, is a network protocol that can be used to perform network services. We use it mainly for accessing remote servers to perform activities in the terminal shell. It is a cryptographic protocol and helps in accessing remote servers securely.

A notable feature of SSH is port-forwarding/tunneling. This feature enables us to forward our traffic via an SSH connection between an SSH client machine and an SSH server machine.

SSH Port Forwarding

SSH forwarding comes in quite handy when we need to transport network data for unencrypted services, bypass firewalls, or even circumvent the lack of a proper static IPV4 Address (by using servers in the cloud). Using this, we can forward TCP-based traffic via different ports to the SSH machine server. The types of port forwarding available are:

Local

In local port forwarding, the SSH connection of the SSH client is forwarded to the destination server. For example, let's assume we have a website running at port 80 in our local system. If we forward port 80 using local port forwarding, then the website at port 80 can also be accessed by the destination server.

Before port forwarding, the website was only accessible from our local system.

Remote

In remote port forwarding, the SSH connection is forwarded from the SSH server to the destination server.

Let's say, in a remote server, we have a database service running at port 5432. If we perform remote port forwarding for that particular port, we can also access the database service locally.

Previously it was accessible from the remote server only, but now it can be accessed by us in our own system.

Dynamic

In dynamic port forwarding, the traffic of different applications can be forwarded to the destination server. Instead of forwarding certain ports only, we can forward the traffic of our entire system to the destination server. Whatever we have running at a particular port in our system will be accessible at the remote destination server as well.

Let's see how we can perform SSH remote port forwarding. Our goal will be publicly exposing app traffic from an internal network to bypass the lack of a static IPV4 address.

Service Setup

Let’s set up a service file on our local machine. Doing this will make you never have to manually run the SSH port forwarding, as the service file will take care of that.

Write the file below into the /etc/systemd/system directory.

[Unit]
Description=SSH Reverse Proxy
After=syslog.target network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=simple
User=example
ExecStart=/usr/bin/ssh -o ServerAliveInterval=120 -R {remote_server_port_number}:localhost:{local_server_port_number} -NT remote-tunnel -v
Restart=always
RestartSec=10
SyslogIdentifier=reverse_proxy

[Install]
WantedBy=multi-user.target
Example of a System Service File

Key components from the contents of the system file specified above are:

  1. remote-tunnel: This is the name of the SSH config specified in ~/.ssh/config. It can be anything you want to use.
  2. User=example: The user directive is very important as it specifies the user ID to be used when running the service. For example, if the user you specified does not contain the SSH configuration for remote-tunnel in their /home/example/.ssh directory, then the service will fail.
  3. ExecStart=/usr/bin/ssh -o ServerAliveInterval=120 -R {remote_server_port}:localhost:{local_server_port} -NT remote-tunnel -v: This is the command that will perform remote SSH port forwarding. ExecStart executes the command and its parameters.
  4. SyslogIdentifier=reverse_proxy: This will prepend the line reverse_proxy in the system log file for logs generated by the service, making it easy to identify the logs originating from this system service. To view the logs, you can use journalctl -u service_name.service -f.
⚠️
Make sure that the ports in both servers are not being used. Otherwise, SSH tunneling will fail.

Now let's make the system aware of the new service file, enable it, and start the service. For that, you can use the following commands.

To make the systemctl daemon aware of changes:

sudo systemctl daemon-reload
Command for reloading systemctl daemon

To enable and start the service:

sudo systemctl enable service_name.service && sudo systemctl start service_name.service
Command(s) for enabling and starting service

To check the service status:

sudo systemctl status service_name.service
Command for viewing service status

SSH Config Setup

As you can see in the service file provided in a few sections above, I used the user example. Replace the user example with your local or root user and set up the SSH configuration in the user's home directory.

  1. Generate an SSH key
ssh-keygen -t ed25519 -C “ssh@example.local”

2. Create an SSH config file

Add the following details into the config file at ~/.ssh/config. Create the file if it does not exist.

Host remote-tunnel
    HostName 192.168.1.99
    User remote-user-name
    IdentityFile ~/.ssh/id_ed25519
SSH connection configuration details

Here we've specified,

i. Host = Identifying name of the config. For example, you can now run ssh remote-tunnel instead of using remote-user-name@192.168.1.99 for SSH connections.

ii. HostName = IP address of the remote server.

iii. User = Username of the user account we will use to SSH into the remote server.

iv. IdentityFile = Path to the SSH private key.

Finally, we’re ready to perform remote port forwarding.

ℹ️
If the SSH configuration details are not found in the home directory of the users specified in our service file, then the service fails with an error code 255.

Further details can be gained by viewing logs of the service file using systemctl status or journalctl -f commands.

Exposing the Service Publicly

Set up a domain –  A record to point towards the remote server's IP address.

I’ll be using the example.com.local domain which I've pointed to 192.168.2.199.

⚠️
Make sure to apply a firewall to the remote server to allow access only to port 22, 80, 443.

This is just a security practice to make sure other services in your remote server are not accessible publicly unless you want them to be publicly accessible.

Now set up an Nginx configuration file in the sites-available directory. We will route web traffic that is received by Nginx to the local port 8980.

This is also known as reverse proxying, where requests received at one server are then forwarded to another server either in a remote location or locally available in our system.

 server {

   server_name example.com;

   location / {
        proxy_pass http://127.0.0.1:8980;
        include proxy_params;
    }
Nginx Reverse Proxy Configuration

Now symlink it to /etc/nginx/sites-enabled using the command below:

ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Finally, we'll generate an SSL certificate. First, let's install Certbot.

sudo apt update && sudo apt install certbot python3-certbot-nginx

Now we can generate the certificate using the command:

certbot --nginx

The Nginx plugin of Certbot will detect the Nginx configuration available in the sites-enabled directory and then give us options to generate a certificate for that particular server name. Once generated, it will also write the SSL configuration in the specific Nginx configuration, making it an easy, one-stop solution for setting up SSL/TLS encryption for our websites.

Finally, if all goes well, we should be able to access the service hosted internally in our local server from the public internet on the domain you've assigned it to.

Please comment below if you have any queries, I try to regularly update my articles to ensure legibility.