How to expose any port to the Internet without exposing your private IP (for free).

Today I will show you how to easly setup exposure of any port without exposing your IP, this solution also bypasses situation when your ISP is not giving possibility to have private (own) IP address which is not behind any NAT.

I used this solution to create access (for myself) to SSH and locally hosted security camera system at my home.

1. Setup NGrok

First of all you need to have an ngrok account[1] - free account is enough for our purposes. Generally ngrok is a reverse-proxy service where you need tun ngrok client on target machine, client enables connection with ngrok server and then you are able to connect with your machine (where ngrok client is running) via Internet.

Free acount forwards local port as random remote port (usually under const hostname: 0.tcp.ngrok.io for tcp tunnels)

After you have account you need to download client and activate account with your API key (follow the instuctions from web service).

2. Verify that tunnel works

Create ngrok config file (under ~/.ngrok2/ngrok.yml):

authtoken: <YOUR_TOKEN_HERE>
tunnels:
        sshglob:
                proto: tcp
                addr: 22

Above config setups tunnel sshglob which forwards locally hosted tcp/22 - in this case SSH service. You should change these values according to your needs.

Now you can try to start ngrok ngrok start sshglob:

ngrok by @inconshreveable                                                            (Ctrl+C to quit)

Session Status                online
Account                       whoami (Plan: Free)
Version                       2.3.35
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    tcp://0.tcp.ngrok.io:13036 -> localhost:22

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

Now you should be able to connect with ssh (in my case) with command: ssh -p 13036 whoami@0.tcp.ngrok.io.

If you don’t have any issues with accessing you machine over Internet you are free to follow next steps :) (otherwise check Troubleshooting section).

3. Bypass random port issue

If you run ngrok couple of times you should notice that every time you service is running at random port, to bypass this issue you can by a premium or make some additional steps presented below.

Install dependencies

sudo apt install -y python3 python3-pip curl jq
pip3 install telegram

Setup Telegram bot

For myself I decided to create Telegram[2] bot which will send me external hostname and random IP address. Ofc you need to have created Telegram account to follow next steps.

  1. Start conversation with telegram Botfather.
  2. Type commands in Botfather conversation:
/newbot
mysuper_bot # replace "mysuper" with your bot name
  1. Copy API key and start conversation with your bot
  2. Type something in the chat & run the below code:
from sys import argv
import telegram

bot = telegram.Bot(token="YOUR_API_KEY")
print(bot.getUpdates())
  1. Copy chat_id
  2. Create script tsend.py:
from sys import argv
import telegram

bot = telegram.Bot(token="YOUR_API_KEY") # FIXME your api key
chat_id = 1234 # FIXME YOUR_CHAT_ID
raw_msg = argv[1].replace('"','').replace('tcp://','')  # "tcp://0.tcp.ngrok.io:10361"
ip,port = raw_msg.split(':')

message = f'{ip}:{port}'
bot.send_message(chat_id=chat_id, text=message)
  1. Check if you are able to send message to you from your bot by typing python3 tsend.py hello.

Make your service always available

Final step, below script will:

  1. Start ngrok tunnel if it is not running
  2. Send you actual IP:PORT thanks to telegram bot

Below script is designed to be run from cron, thanks to this solution it will be run automatically i.e every 5 minutes: */5 * * * * path/to/monitor.sh

# monitor.sh
#!/bin/bash

NGROK='/usr/local/bin/ngrok'
DIR="$HOME/ngrok-apps" # directory containing this script and tsend.py
URL_DIR='/tmp/ngrok-url'

# check if it's working
if [ ! "$(pidof ngrok)" ]; then
        echo "[ngrok] is not working, starting new instance..."
        $NGROK start sshglob > /dev/null &
        sleep 3
fi

# get current status of tunnel
NEW_STATUS="$(curl -s http://127.0.0.1:4040/api/tunnels | jq '.tunnels[0] .public_url')"
echo "[ngrok] $(date) $NEW_STATUS"
OLD_STATUS=""
if [ -e "$URL_DIR" ]; then
        OLD_STATUS="$(cat $URL_DIR)"
fi

# check if differ
if [ "$NEW_STATUS" != "$OLD_STATUS" ]; then
        echo "[ngrok] New status detected! Updating IP"
        echo "$NEW_STATUS" > "$URL_DIR"
        python3 "$DIR/tsend.py" "$NEW_STATUS"
fi

If everything works you should receive notification to your telegram if IP or port will change.

Troubleshooting

My ngrok tunnel is started, but I can’t access service

Is this service running also on localhost? Or can you reach shown ngrok host (IP)?

Can I use something else than telegram?

Yes, but I had already telegram in place. If you need you can use i.e Discord, Slack, mail, etc. (but you need to modify tsend.py script)

References

  1. https://ngrok.com/
  2. https://telegram.org/