Skip to content

Instantly share code, notes, and snippets.

@soheilhy
Last active October 23, 2024 08:19
Show Gist options
  • Save soheilhy/8b94347ff8336d971ad0 to your computer and use it in GitHub Desktop.
Save soheilhy/8b94347ff8336d971ad0 to your computer and use it in GitHub Desktop.
How to proxy web apps using nginx?

Virtual Hosts on nginx (CSC309)

When hosting our web applications, we often have one public IP address (i.e., an IP address visible to the outside world) using which we want to host multiple web apps. For example, one may wants to host three different web apps respectively for example1.com, example2.com, and example1.com/images on the same machine using a single IP address.

How can we do that? Well, the good news is Internet browsers send the domain name inside HTTP requests and all we need to do is to parse the requested domain name and URL and then route the HTTP request to the actual web server.

Oh, do I really need to parse HTTP requests? You can if you really want to, but there are lots of tools and technologies that readily do this for you. In this tutorial, we walk you through how you can use nginx to proxy multiple web applications.

Install nginx

CDF @UofT

We have prepared pre-copmiled binaries for your. You need to download nginx.tar.gz and uncompress it:

$ wget http://www.cs.toronto.edu/~soheil/csc309/nginx.tar.gz && tar -xvzf nginx.tar.gz
  • It creates an nginx directory for you. The config file is in nginx/conf/nginx.conf.
  • We have provided a script named nginx in the directory. To run nginx, go to the nginx directory (cd nginx) and run ./nginx ....

Ubuntu

Install nginx using apt-get:

$ sudo apt-get install nginx

Notes:

  • The part of the nginx's config file we need resides in /etc/nginx/sites-enabled/default.
  • To edit the config file or run nginx, you need to use sudo.

Mac OS

Install homebrew, and then install nginx using brew:

$ brew install nginx

Notes:

  • nginx's config file is in /usr/local/etc/nginx/nginx.conf.
  • To edit the config file or run nginx, you need to use sudo: sudo nano /usr/local/etc/nginx/nginx.conf and sudo nginx ...

Configuration

Step 1 -- Booting Servers for Virtual Hosts

Write three different node applications running on different ports (say 8080, 8181, 8282) on your machine.

Step 2 -- Configure nginx's Port

To do so, you need to edit your nginx config file.

In the config file, find the server section:

server {
    listen       80;
    ...
    location / {
       ...
    }
    ...
}

If you're using CDF, make sure you change 80 to a vacant port number (ask for one from your instructor). If not, you can keep using 80 or change the port if you will.

Test nginx

  1. Run ./nginx on CDF, or run sudo nginx on your local machine.
  2. Open the browser and log on to localhost:$PORT (replace $PORT with the port number you configured for nginx).

Step 3 -- Configure /

Let say we want to configure nginx to route requests for /, /blog, and /mail, respectively onto localhost:8080, localhost:8181, and localhost:8282.

                  +--- host --------> node.js on localhost:8080
                  |
users --> nginx --|--- host/blog ---> node.js on localhost:8181
                  |
                  +--- host/mail ---> node.js on localhost:8282

To route /, you need to edit your nginx config file.

In the config file, find the server section:

server {
    listen       80;
    ...
    location / {
       ...
    }
    ...
}

This section is simply telling nginx how it should serve HTTP requests.

Now, change the location section to this snippet:

server {
    listen       ...;
    ...
    location / {
        proxy_pass http://127.0.0.1:8080;
    }
    ...
}

proxy_pass simply tells nginx to forward requests to / to the server listening on http://127.0.0.1:8080.

Step 4 -- Reload nginx's Configuration

To reload nginx's configuration run: nginx -s reload on your machine.

Referesh your browser. Do you see the output from your node.js application? If yes, you are all set. If no, there is a problem with your config.

Step 5 -- Add /blog and /mail

To redirect /mail and /blog, you simply need to add new entries the location section in the config file:

server {
    listen       ...;
    ...
    location / {
        proxy_pass http://127.0.0.1:8080;
    }
    
    location /blog {
        proxy_pass http://127.0.0.1:8181;
    }

    location /mail {
        proxy_pass http://127.0.0.1:8282;
    }
    ...
}

Step 6 -- Reload Your nginx Configuration

Run nginx -s reload on your machine.

Log onto localhost:$PORT/blog in your browser. Do you see the output from your second node.js application?

Then log onto localhost:$PORT/mail. Do you see the output from your third node.js application?

If yes & yes, you are all set. If no, there is a problem with your config.

Step 7 -- Rewriting Requests

Now as you might have noticed in Step 6, nginx sends the same HTTP request to your node.js web apps which results into a 404 error. Why? Because, your node.js web application serves requests from / not from /blog and /mail. But, nginx is sending requests to /blog and /mail.

To fix this issue, we need rewrite the URL so that it matches the URL you can serve on your node.js applications.

To correctly rewrite URLs change your config file to match the following snippet:

server {
    listen       ...;
    ...
    location / {
        proxy_pass http://127.0.0.1:8080;
    }
    
    location /blog {
        rewrite ^/blog(.*) /$1 break;
        proxy_pass http://127.0.0.1:8181;
    }

    location /mail {
        rewrite ^/mail(.*) /$1 break;
        proxy_pass http://127.0.0.1:8282;
    }
    ...
}

This rewrite commands are simple regular expressions that transform strings like /blogWHAT_EVER and /mailWHAT_EVER to /WHAT_EVER in the HTTP requests.

Step 8 -- Reload and Test.

All set?

Exercise 1

Configure your nginx to redirect URLs from /google to http://www.google.com

Step 9 (optional) -- Redirecting Based on Host Name

Let say you want to host example1.com, example2.com, and example3.com on your machine, respectively to localhost:8080, localhost:8181, and localhost:8282.

Note: Since you don't have access to a DNS server, you should add domain name entries to your /etc/hosts (you can't do this on CDF machines):

...
127.0.0.1 example1.com example2.com example3.com
...

To proxy eaxmple1.com we can't use the location part of the default server. Instead we need to add another server section with a server_name set to our virtual host (e.g., example1.com, ...), and then a simple location section that tells nginx how to proxy the requests:

server {
    listen       80;
    server_name  example1.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

server {
    listen       80;
    server_name  example2.com;

    location / {
        proxy_pass http://127.0.0.1:8181;
    }
}

server {
    listen       80;
    server_name  example3.com;

    location / {
        proxy_pass http://127.0.0.1:8282;
    }
}

Simple, ha?!

@temirlan42
Copy link

Thanks a lot

@kapil26021994
Copy link

Hi @railsfactory-suriya in my case my angular app deployed on www.test.com/ui so in location tag we need to add location /ui it's correct or wrong can you confirm Me regarding this one?

@WilsonCampbell-BMOC
Copy link

WilsonCampbell-BMOC commented May 7, 2024

I'm running an nginx on an AWS EC2 linux ubuntu instance. I have the location file setup and its been running fine. however, I ran a streamlit install update and now all of a sudden it isn't. What is curious is that the error I'm getting has to do with a favicon from the looks of it. There is no favicon.ico file in the app directory - I do not know why it keeps doing this, but it gives a 504 Gateway timeout error.

Here is the server file details (cleaned of identifying info):

  server {
      listen 80;
      server_name subdo.name.app;
      #redirect HTTP to HTTPs
      return 301 https://$host$request_uri;
  }
  
  server {
      listen 443 ssl;
      server_name subdo.name.app;  # Replace with your actual domain name
  
      # SSL configuration
      ssl_certificate /path/to/fullchain.pem; # managed by Certbot
      # Path to your SSL certificate
      ssl_certificate_key /path/to/privkey.pem; # managed by Certbot
      # Path to your private key
  
       location / {
	  # Basic authentication configuration
          auth_basic "Restricted Content";  # This is the message shown in the login prompt
          auth_basic_user_file /path/to/.htpasswd;  # Path to the file containing the username and password
  
          proxy_pass http://XX.XXX.XX.XXX:8501;
          proxy_http_version 1.1;
          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;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
      }
  }

Here is what the nginx error log is showing:

  2024/05/07 11:12:23 [error] 1991#1991: *7 upstream timed out (110: Unknown error) while connecting to upstream, client: XXX.XXX.X.X.X, server: subdo.name.app, request: "GET / HTTP/1.1", upstream: "http://XX.XXX.XX.XXX:8501/", host: " subdo.name.app"
  2024/05/07 11:13:23 [error] 1991#1991: *7 upstream timed out (110: Unknown error) while connecting to upstream, client: XXX.XXX.X.X.X, server: subdo.name.app, request: "GET /favicon.ico HTTP/1.1", upstream: "http://XX.XXX.XX.XXX:8501/favicon.ico", host: " subdo.name.app", referrer: "https:// subdo.name.app/"

A few other points on this.

  1. I've not previously had to have a .streamlit directory with a config.toml - its been working without such a thing up till now.
  2. It is running fine when I go to http://XXX.XX.X.XXX:8501 and has no errors reporting in the terminal when I run it.
  3. AWS Security Group is setup to permit traffic appropriately via HTTP and HTTPS, and the SSL certificate are good to go.
  4. The only thing I did this morning was run a pip install of the requirements.txt file, none of which made a change as all libraries were already installed. I was just testing the procedure for relaunching the app on a new server, using the existing one.
  5. The app is being run in a tmux session that I then detach from once its running - again, no issues running it and the 8501 port shows it running fine without any error reporting.

Has anyone else run into a similar issue - able to resolve the problem?

@pravynandas
Copy link

@WilsonCampbell-BMOC it seems all the configuration looks fine. I would first look into the upstream's health considering the timeout. "upstream timed out (110: Unknown error)"....

@WilsonCampbell-BMOC
Copy link

@WilsonCampbell-BMOC it seems all the configuration looks fine. I would first look into the upstream's health considering the timeout. "upstream timed out (110: Unknown error)"....

Not sure what you mean by this - I am not a server guy and am going at this in a rather brute force manner - just going with ChatGPT recommendations and general understanding of running a server from research, online videos, and books I've picked up on the subject.

If by upstream you mean the app running on 8501, it is running fine when I go to its web address via ip:port.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment