Reverse proxy with custom domain

Have you ever run into the situation where you want to access a server behind multiple layers of firewall and with no public IP? If so, please read on. In this post, I’ll be sharing how I can successfully access my server with a custom domain.


You are gonna need a few things before we move on, they are listed below:

  • Your server (of course!)
  • A publicly accessible server
  • A domain name (optional)

Here let’s assume the public server has an IP of, and your domain name is

Personally, the server I have is a Ubuntu 16.04 LTS system. And the server with public IP is a VPS hosted on DigitalOcean running Debian 8. My steps below should work for a similar configuration. You might need some small tweaks to cope with different OS.

SSH tunneling

The first step is to create a SSH tunnel between the public server and your server. This enables you to access a certain port of your server by connecting to a port of the publicly accessible server. For example, if you create a tunnel between port 8888 of the public server and port 8080 of your server, whenever you access port 8888 of the public server, your traffic is tunnelled to port 8080 of your server. Hence, port 8080 of your server is exposed to the Internet. To create a tunnel, first edit the file /etc/ssh/sshd_config on your public server. Make sure you have the following line in that file:

GatewayPorts yes

This enables remote port forwarding, which is disabled by default. Now restart the ssh daemon on your public server with:

$ sudo service ssh restart

This reloads your new configuration. After that, let’s connect to your public server from your server sitting behind firewalls with SSH to set up a remote port forwarding. Run the following command on your server:

$ ssh -R 8888:localhost:8080 user@

This creates the actual tunnel between and port 8080 of your server. Remember to change user to your actual username.

Adding a domain name

After the previous steps, you can already access your server with If you think this is enough, you can stop here. However, I think adding a domain name makes the address easier to remember. I’ll skip the steps on how to map a domain name to a server. Instead, I’ll emphasize on how to map port 80 of your domain to, so that you don’t have to append :8888 everytime you access it. To make it work, we need to install nginx, which is a lightweight HTTP server and reverse proxy, on your public server. On a debian/ubuntu system, run the following command:

$ sudo apt-get install nginx

Now let’s create a file at /etc/nginx/sites-available named server and add the following to it:

server {
  server_name   “~^example\.com$”;

  location / {
    proxy_set_header  X-Real-IP  $remote_addr;
    proxy_set_header  Host $host;

And let’s create a soft link for it:

$ sudo ln -s /etc/nginx/sites-available/server /etc/nginx/sites-enabled/server

Now reload the configuration by restart the nginx service:

$ sudo service nginx restart

You should now be able to access to use port 8080 of your server. Great! If you want a second level domain such as, change ”~^example\.com$” to ”~^test\.example\.com$” and restart nginx.

Redirecting WebSocket for Jupyter

If you happen to run Jupyter like me, you may notice that you can access its pages, but not the notebook kernels. This is because Jupyter uses the WebSocket protocol for bi-directional communication, which is different from the normal HTTP protocol. Therefore, we need to change the configuration a little bit for this. Change /etc/nginx/sites-available/server to the following:

server {
  server_name   “~^test\.example\.com$”;

  location / {
    proxy_set_header  X-Real-IP  $remote_addr;
    proxy_set_header  Host $host;
    proxy_set_header  Upgrade $http_upgrade;
    proxy_set_header  Connection “upgrade”;
    proxy_read_timeout 86400;

This will upgrade your connection from HTTP protocol to WebSocket protocol. And Jupyter should work now. Don’t forget to restart the nginx server.

Relevant links

Here are some of the references I used when I was doing this:

Hope this helps!

comments powered by Disqus