There are occassions when you want to expose a local port to the world so it can be accessed publicly on the internet.
For example, if you want to:
- Share you local development environment publicly
- Be able to receive webhooks from external services for your local development environment like Stripe webhooks
- Expose a local database to the internet via a remote server
One option is to log in to your router, typically
https://188.8.131.52/ or something similar,
and configure port forwarding. That will let you tell the router to take incoming traffic for
a specific port and send it your local computer. This can be annoying because you have to undo
the change when you are done, which is easy to forget about, and you may not have access to
your router with admin privileges at all. You can also run in to port conflicts.
Another option is to use a remote host, like a VPS rented from a service like Digital Ocean to expose your port to the internet. You can do this by using SSH port forwarding (tunneling) to securely forward a port from your local computer to a port on the Digital Ocean VPS. In turn, you can expose that port on the VPS to the internet.
This example is like an exercise in setting up your own ngrok service.
This example takes the following scenario:
- You are running a local development web server that listens locally
on your machine at
- You want to allow someone from the internet to connect to your web server, but you are behind a router and you can't configure port forwarding on the router.
- You have a remote machine hosted on Digital Ocean with DNS configured as
Given this scenario, we will look at using SSH tunnels to securely forward your local port to the remote server so anyone from the internet can reach it.
Set up the local service
First, you will need some service on your local machine that you want to expose publicly from
another host. In this example we'll create a simple HTTP server using Python that will listen
# On your local machine, serve HTTP on 127.0.0.1:8000 python3 -m http.server --bind localhost 8000
See my tutorial on one-line HTTP servers for other ways to create quick HTTP servers with Ruby, Python, OpenSSL, PHP, and other tools. Of course, it doesn't have to be an HTTP server, it can be any TCP/IP service.
Initialize SSH port forwarding
While your HTTP server or other service is listening locally on your machine, use another terminal and open an SSH connection with the remote server. The one special thing about this SSH connection is that we want to specify a remote port forward so the remote server will expose a port that actually forwards back to your home machine.
# This will SSH to `my-remote-host.com` and while # the session is open, the remote server will # start listening on on port 9999 and any # connection it recieves will get forward # straight back to port 8000 on your local computer ssh -R 9999:localhost:8000 my-remote-host.com
Verify port forwarding
On the remote server in your SSH shell, you can check to see what IP and ports
are listening with
# On the remote server netstat -ntl # Show listening ports, verify port 9999 is listening
You may only see it listening only locally on
127.0.0.1:9999 instead of
We'll look at how to reconfigure SSHD to open the ports publicly in the next section.
Either way, you can use curl on the remote to check that the port is being forwarded locally at minimum.
# On the remote server # Test the port forwarding. Should get back HTML curl localhost:9999
Expose the forwarded port publicly
If your forwarded port is only listening locally on the remote server,
that means SSHD has
GatewayPorts set to
To expose the port to the internet you have a couple options:
- You can use a reverse proxy like nginx or Apache to listen on a public address like 0.0.0.0:80 and forward that to the local address 127.0.0.1:9999. (((see my nginx tutorial on reverse proxies)))
- You could use any other reverse proxy like http://mitmproxy.org/.
- Re-configure SSHD so it exposes forwarded ports on all interfaces instead of just localhost.
To reconfigure SSHD so it
does not restrict forwarded ports to only listen locally,
you can update your SSHD config file, usually
sudo vim /etc/ssh/sshd_config # Set GatewayPorts yes sudo systemctl restart sshd
Please keep in mind the ramifications of turning the
yes. If other system users are unaware of this, they could potentially
expose sensitive data they did not intend to.
GatewayPorts turned on, you can then hit
the port from any host on the internet.
For example if you were shelled in to
someone on the internet could do:
Whenever the SSH connection is closed, so is the port tunnel so it's only temporary making it easy to tear down when you're done testing without any reconfiguration to your router.
SSH tunnel without the shell
-N flag to start SSH without opening the shell.
-f flag to have SSH run in the background after starting.
You will need to call
kill manually if you run it in the background.
ssh -f -N -R 9999:localhost:8000 my-remote-host.com
Setup $HOME/.ssh/config file
You can setup a host entry in your
$HOME/.ssh/config file so it
includes a remote port forward. Here is an example entry:
Check out my SSH tips post for more tips on using SSH.
# $HOME/.ssh/config Host mysandbox HostName my-sandbox.com User myusername # RemoteForward <remote_port> <local_address> RemoteForward 9999 localhost:8000
You could then connect using the following command and it will include the port forward:
ssh -f -N mysandbox
A note about security
Although an SSH tunnel is secure between your local machine and the remote host, it has no control over what you do with that exposed port on the remote machine. There is no inherent authentication, authorization, or encryption on anything other than communication between your local machine and the remote host over the SSH connection. If you expose the port to the internet on the remote host, you are potentially exposing your home computer to the internet with no security whatsoever.
You will need to implement your own authentication like HTTP Basic Auth and your own encryption like SSL to make it more secure on the public internet. Just be aware that you could be exposing your local machine to the public internet unencrypted so be very conscious and careful with what you do.
To distill the steps necessary for this process, the summary is:
- Have a service lisetning on your local machine like an HTTP server.
- Connect to the remote host using SSH with the
-Rflag for remote port forwarding.
On the local machine, start an HTTP server on
# On the local machine, start an HTTP server on localhost:8000 python3 -m http.server --bind localhost 8000
Then forward the port over SSH.
# Also on the local machine ssh -R 9999:localhost:8000 my-remote-host.com
You can then hit the port locally while on the remote server.
# From the remote machine curl http://localhost:9999
If SSHD is configured properly, then you can also from any host on the internet hit port 9999 on the remote server.
# From the internet (if SSHD configured with `GatewayPorts yes`) curl http://my-remote-host:9999