Search code examples
node.jsamazon-web-servicesnginxreverse-proxyamazon-linux-2

Nginx on ec2 instance does not serve static files


What was working

I have MERN stack dashboard up and running on AWS EC2 instance.

  • Ec2 instance is at ec2...aws.com
  • React app is served on port 5004.
  • Node app is running on port 5003.

What was changed

The problem started when I set up the domain at GoDaddy to forward (with masking)
from somebusiness.com to ec2.....aws.com.

First, I set up NGINX reverse proxy to serve / as my react app (frontend) and /api as my node app (backend)
(you will find nginx code down below later)

http://somebusiness.com/ <- opens up React app and is working correctly
http://somebusiness.com/api/heartbeat <- not working as I expected (heartbeat is just endpoint to check if the app is alive) but for some reason, does not return application/JSON but rather a text/HTML webpage with the correct 'real URL' inside a frame of some kind:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>

<head>
  <title>Somebusiness Dashboard</title>
  <meta name="description" content="">
  <meta name="keywords" content="">
</head>
<frameset rows="100%,*" border="0">
  <frame src="http://ec2-...aws.com/api/heartbeat" frameborder="0" />
</frameset>

</html>

The actual URL in the frame is the one that is working as expected:
http://ec2-...aws.com/api/heartbeat <- is working correctly

I think GoDaddy has something to do with this because on GoDaddy I can specify title, description, and keywords for this forwarding. So as backend, I'm forced to use http://ec2-...aws.com/api/ as my backend URL, which is OK for now (it is my secondary problem at the moment since it's just convenient to use the domain name for backend as well)

Main Problem

if i send a request to
http://ec2.....aws.com:5003/uploads/avatars/user1.jpg
the image is loaded OK.

So if i send a request to
http://ec2.....aws.com/api/uploads/avatars/user1.jpg
the image is NOT loaded.

So to recap: http://ec2-...aws.com/api/ is working OK for routes and requests, but not for serving static files. That leads me to believe my nginx setup is wrong, and I've spent countless hours trying all kinds of different setups. Here I present where i left it:

Source & code for help

Since I'm using Amazon Linux 2, I've used its tools to install nginx1 and set-up reverse proxy. When i used NGINX on my other VPS, there was a bit different structure to it (sites-enabled & sites-available folders). But on this nginx there is only nginx.conf file where I did the setup. (I guess it doesn't make difference, just wanted to note that)

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  eyeratereviews.com;
        root         /home/ec2-user/somebusiness-web-backend;

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

        location / {
            proxy_pass http://127.0.0.1:5004;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }

        location /api {
            root /home/ec2-user/somebusiness-web-backend/uploads/avatars;
            default_type application/json;
            proxy_pass http://127.0.0.1:5003;
            # Following is necessary for Websocket support
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

My node.js project folder structure is like this
...
controllers/
models/
static/
- pictures/
-- logo.png
uploads/
- avatars/
-- user1.jpg
-- user2.jpg
-- ...


Solution

  • Your existing code does not attempt to remove the /api from the front of the URI before passing it upstream. Add a trailing / to both the location and proxy_pass values. For example:

    location /api/ {
        proxy_pass http://127.0.0.1:5003/;
        ...
    }
    

    See this document for details.


    Alternatively, use a rewrite...break to modify the URI instead.

    For example:

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

    See this document for details.