Skip to content

Instantly share code, notes, and snippets.

@soheilhy
Last active March 19, 2024 04:33
Star You must be signed in to star a gist
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?!

@alator21
Copy link

That was super helpful. Thanks!

@railsfactory-suriya
Copy link

railsfactory-suriya commented Apr 2, 2020

@ALL

Couldn't make it work with docker container with nodejs application. Am getting 404 error. Could someone help me on this.

Note -
Am running both nodejs and nginx as separate docker container
** I don't have the /test route in nodejs my application.**

server {
    location /test {
        rewrite ^/test(.*) $1 break;
        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_pass http://localhost:4000;
    }
} 

Nginx error log

2020/04/02 06:53:58 [error] 7#7: *2 "/etc/nginx/html/index.html" is not found (2: No such file or directory), client: 172.17.0.1, server: , request: "GET / HTTP/1.1", host: "localhost:8080"

@railsfactory-suriya
Copy link

@ALL

This above issue got fixed when i changed the config like below.

server {
  location /test {
      rewrite ^/test/(.*) /$1 break;
    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_pass http://localhost:4000/;
  }
}

@dodycode
Copy link

Thanks a lot!

@MKOInvest
Copy link

thanks a lot

@taylorbakow
Copy link

taylorbakow commented Jun 2, 2020

On step 6, how would you access the different URLs WITHOUT exposing the ports (A user shouldn't be seeing the port number in the URL)...

@IKHMedia
Copy link

IKHMedia commented Jun 8, 2020

thanx, this works for me, 1 thing I want to know...is there any disadvantages in this approach? Will my server get expose for any attacks?

Anytime you open your web server to the public by using your home internet or even a VPS with port forwarding (AKA Opening any ports to the public you do risk this complication. If you follow this tutorial make sure you have your router or VPS set to only allow IP addresses you want or use a provider like cloudflare which has a free version available if you are concerned about security. As always you should update any software and update your packages on your linux boxes to ensure this doesn't happen.

@btphan95
Copy link

btphan95 commented Jun 9, 2020

Thank you very much!

@thisissmohan
Copy link

@ALL

This above issue got fixed when i changed the config like below.

server {
  location /test {
      rewrite ^/test/(.*) /$1 break;
    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_pass http://localhost:4000/;
  }
}

thank you so much :)

@tashrifbillah
Copy link

tashrifbillah commented Jul 17, 2020

I wrote the following config:

server {
     location / {
             proxy_pass http://127.0.0.1:8082/;
     }
}

Only to get a Bad Gateway error:

<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.16.1</center>
</body>
</html>

@SoheilY

Edit:
My problem went away magically. While I have not been able to figure the reason behind the error, I think the following may have fixed it:

nginx -s stop
service nginx restart
setsebool -P httpd_can_network_connect 1

@tashrifbillah
Copy link

server {
    listen       443 ssl http2 default_server;
    listen       [::]:443 ssl http2 default_server;
    server_name  pnlservers.bwh.harvard.edu;

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

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

    location /luigi {
        proxy_pass http://127.0.0.1:8082;
    }

Each of the above location blocks works fine individually against /, but when I put the latter under /luigi, I get:

curl -L https://pnlservers.bwh.harvard.edu/luigi

<html><title>404: Not Found</title><body>404: Not Found</body></html>

Rewriting URL didn't help. Did anyone else face the same issue?

@joweste
Copy link

joweste commented Aug 20, 2020

I am trying to set multiple root to locations in my app.

My mainapp is served from root: "../gestan-cloud/dist"

But, I have a viewer folder with "../gestan-cloud/node_server_app/viewer" with other "index.html".

I need to put that file into a iframe in a page of my app.

My mais app works well, but I am unable to make the iframe works.

The "index.html" from "../gestan-cloud/node_server_app/viewer" is never showing into iframe.

My page with problem loading the iframe. "/gestdicom/default.html" is a fake url, used only to nginx redirect to "../gestan-cloud/node_server_app/viewer/index.html":

<div>
    <iframe
      src="/gestdicom/default.html"
      width="100%"
      height="100%"
      frameborder="0"
    ></iframe>
  </div>

my app folder:

\mainApp
|	\gestan-cloud
|	   \dist
|		   index.html
|		   \css
|	   \node_server_app
|		   \viewer
|			  index.html
|
|	\nginx-1.18.0
|		\conf		  
|		\docs
|		....

//Here is my nginx file conf

worker_processes  2;
error_log  logs/error.log;
error_log  logs/error.log  notice;
events {
    worker_connections  1024;
}

http {
	rewrite_log on;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    gzip  on;

	# Enable SSL
	ssl_certificate  ../ssl/cert.pem;
	ssl_certificate_key ../ssl/key.pem;
	ssl_session_timeout 5m;
	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
	ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
	ssl_prefer_server_ciphers on;

    server {
       listen          80;
	   listen         [::]:80;
       return 301 https://$host$request_uri;
    }
	
	server {
		listen 443 ssl;
		server_name gestan1.myapp.local;
		
		#main app index.html
		location / {
		 root     ../gestan-cloud/dist;
		 index    index.html index.htm;
		 try_files $uri $uri/ /index.html;
		}
		location ~ ^/(api|uploads|public)/ {
			access_log off;
			proxy_pass http://localhost:3001$request_uri;
		}
		
		#socket io
		location ~* \.io {
		  access_log off;
		  include proxy_io_params.conf;
		  proxy_pass http://localhost:3001;
		}


		#redirect para o server ohif
		location /gestdicom/ {
		  root     ../gestan-cloud/node_server_app/viewer;
		  index  index.html;
		  rewrite ^/gestdicom(.*)$ /$1;	
		  try_files $uri $uri/ =404;
		}
		
		
		error_page   500 502 503 504  /50x.html;
		location = /50x.html {
			root   html;
		}
	}
}

@pravynandas
Copy link

If you want to redirect to URI, you need to remove one slash. For e.g, rewrite ^/blog(.) /$1 break; should be changed to rewrite ^/blog(.) $1 break;
Remove '/' before $1.

Great article and this one helped me in fixing redirecting to another port on a different path. Thanks @sisingh

@raschmitt
Copy link

raschmitt commented Dec 22, 2020

According to http://serverfault.com/a/725433 it seems that the step 7 can be done without URL rewriting and thus better perfs.

Any thought on this ?

Worked fine for me. Going with this as it makes things simpler.

@medlaam
Copy link

medlaam commented May 4, 2021

@ALL

This above issue got fixed when i changed the config like below.

server {
  location /test {
      rewrite ^/test/(.*) /$1 break;
    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_pass http://localhost:4000/;
  }
}

thanks bro, finaly a working code

@Mushud
Copy link

Mushud commented May 23, 2021

Great

@xino1010
Copy link

Thank you!

@MichaelVoelkel
Copy link

MichaelVoelkel commented Oct 10, 2021

Not sure if it was mentioned but your above server configuration example only works if you want to have /blog and /mail ALSO as part of the URLs in localhost:8181 and localhost:8282 because after proxy_pass you have no URI given and then the whole request matching location will be just APPENDED. I took some time to figure this out.

If instead localhost:8181/ or localhost:8282/ should be the base URLs, you need to add trailing slashes to both proxy_pass directives which effectively add a URI and THEN also add trailing slashes to the location directives (only changing the first but not second place lets you end up with double slashes).

So, for me, this makes much more sense:

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/;
    }
    ...
}

See also http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass which talks about the difference between stating a URI after proxy_pass and not stating one (URI means stating anything AFTER the port)

@cli0
Copy link

cli0 commented Nov 2, 2021

Hello there,
I have a very similar setup to your example but I do not get the same results.

For reference, my application is serving on port 3000 and my configuration file is the following (located in /etc/nginx/sites-enabled)

 server {
      server_name example.com;
      client_max_body_size 100M;

      ssl on;
      ssl_certificate /home/domain-certs/example.crt;
      ssl_certificate_key /home/domain-certs/example.key;

      location / {
              proxy_pass https://127.0.0.1:3000/;
      }

  listen 80;
}

If I go to https://example.com I do not get the result (but err_connection_refused). Yet if I add the port to the uri https://example.com:3000 I get served the correct results. What am I doing wrong here? For reference I also added 127.0.0.1 example.com at /etc/hosts.

Thank you.

@raysubham
Copy link

very helpful.

@fida-ur-rehman
Copy link

thanks u dude

@ryuukk
Copy link

ryuukk commented Mar 2, 2022

does anyone know how can i have a location forward what ever port i put on the client's url?

something like:

server {
 ...
      location /game {
              proxy_pass https://127.0.0.1:$PORT/;
      }
}

@karthikeyan951213
Copy link

Dear Team ,

I need a support Multiple 80 port URL request to redirect to local machine different multiple ports .

I will enter https://example.com1 and https://exampke.com2

Kindly help to redirect config in nginx Ubuntu ??

@horlarme
Copy link

Dear Team ,

I need a support Multiple 80 port URL request to redirect to local machine different multiple ports .

I will enter https://example.com1 and https://exampke.com2

Kindly help to redirect config in nginx Ubuntu ??

The server_name variable can take multiple values separated by space eg. www.example.com www.example2.com and the same server will handle them both.

@karthikeyan951213
Copy link

Thank You lot its working

@karthikeyan951213
Copy link

Dear Team,
How to redirect URL to another server IP address in nginx .

@stonestecnologia
Copy link

@Sky-bits
Copy link

Sky-bits commented Feb 15, 2023

@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?

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