Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save FlyingFathead/cbe69fb1ee1e9b11455f0230ae350cc5 to your computer and use it in GitHub Desktop.
Save FlyingFathead/cbe69fb1ee1e9b11455f0230ae350cc5 to your computer and use it in GitHub Desktop.
Example reverse proxy/tunnel config for nginx to run i.e. a local Flask web server over a remote VPS
#> Created by FlyingFathead, Oct. 14th, 2024
#> https://github.com/FlyingFathead/
#
# Here's an extremely simple example of a reverse tunnel proxy for serving
# i.e. a local Flask over a remote VPS running nginx with htpasswd auth.
#
# .htpasswd with nginx requires 'apache2-utils'.
# Install it with: `sudo apt-get install apache2-utils`
# (on Debian/Ubuntu tree Linux systems)
#
# -----------------------------
# Legend (Simplified Topology):
# -----------------------------
#
# ====================== =================== ========================
# | Your local machine | <==> | Your Remote VPS | <==> | Client, You, Whoever |
# ====================== SSH =================== Open ========================
# (runs i.e. Flask, (nginx) Web The user who connects to
# its API's, etc.) [ acts as the web the local machine's
# server to outside ] services via the web
#
# NOTE:
# =====
#
# Please use HTTPS at all times, even if you're setting up a basic testbed.
# I.e. Certbot can be used to get your https root certs right.
#
# This script is NOT for serious production use, but rather just a overall view
# on how you _can_ set up simple localhost mini web services (like Flask) so
# that they're hosted on a remote VPS over a ssh/autossh reverse tunnel.
#
# I do recommend i.e. adding another layer of authentication on top of Flask
# (i.e. a separate login), token-based authentication (OAuth, JWT), etc.
#
# ------------------------------------------------------------------------------
# You can create a persisting tunnel to your VPS's backend i.e. with:
# ------------------------------------------------------------------------------
#
# $ autossh -vvv -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -N -R 127.0.0.1:"$remote_port":localhost:"$local_port" "$remote_username"@"$remote_hostname"
#
# (note: reverse tunnel keepalives etc may depend on i.e. your ISP's NAT, esp.
# if using a mobile data provider)
#
# -------------
# Placeholders:
# -------------
#
# - <yourvps.com> = your VPS's address / domain name if you have one.
# (same as "$remote_hostname" in the bash script below)
# - <yourtunnel_dir> = the tunnel directory name, assuming that you don't want
# your services at your server's root.
# (i.e. example.com/<yourtunnel_dir>/ <= note: only the name of the directory)
# - <remote_port> = remote port on the VPS to forward
#
#
# - The stuff below goes to your: '/etc/nginx/sites-available/default'
# - 'sudo nginx -t' to test it.
# - 'sudo systemctl reload nginx' to reload nginx after the tests passed.
#
# Feel free to comment if this example needs something. It's provided AS-IS.
# ------------------------------------------------------------------------------
# global; rate limit htpasswd attempts [optional]
# limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
server {
listen 80;
listen [::]:80;
server_name <yourvps.com> www.<yourvps.com>;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}
## HTTPS Server Block
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name <yourvps.com> www.<yourvps.com>;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/<yourvps.com>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<yourvps.com>/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
access_log /var/log/nginx/access.log main;
## Main Site Configuration
location / {
try_files $uri $uri/ =404;
}
# Temporary Test Location // Use to test your IP
location /test-ip {
return 200 "Your IP is $remote_addr\n";
}
location /<yourtunnel_dir>/ {
## Some rewrite ideas here:
# rewrite ^/<yourtunnel_dir>(.*)$ $1 break;
# rewrite ^/api/(.*)$ /<yourtunnel_dir>/api/$1 break;
# rewrite ^/<yourtunnel_dir>/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:<remote_port>/;
# This passes the /<yourtunnel_dir> prefix to Flask in the SCRIPT_NAME header, so Flask knows it needs to adjust the reque>
proxy_set_header SCRIPT_NAME /<yourtunnel_dir>;
# Port where your reverse tunnel is running
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Handle WebSockets if needed
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Dynamically rewrite URLs in the HTML content
sub_filter_once off;
sub_filter 'href="/' 'href="/<yourtunnel_dir>/';
sub_filter 'src="/' 'src="/<yourtunnel_dir>/';
sub_filter 'action="/' 'action="/<yourtunnel_dir>/';
sub_filter 'fetch("/' 'fetch("/<yourtunnel_dir>/';
sub_filter_types application/javascript text/html;
sub_filter 'modalImage.src = "/' 'modalImage.src = "/<yourtunnel_dir>/';
sub_filter 'url: "/' 'url: "/<yourtunnel_dir>/';
sub_filter 'window.location="/' 'window.location="/<yourtunnel_dir>/';
# Ensure sub_filter is applied to HTML files
# sub_filter_types text/html;
sub_filter_types text/html application/javascript;
# Ensure proper redirects under subdirectory
proxy_redirect / /<yourtunnel_dir>/;
# proxy_redirect off;
proxy_buffering off;
# Authentication for this subdirectory
limit_req zone=mylimit;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
}
# Serve API requests via proxy pass
location /api/ {
# limit_req zone=mylimit;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:<remote_port>/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment