Serving static files with nginx

I have some problem, serving static files with nginx instead of meteor.

Here is my nginx site configuration

# HTTP
server {
    listen 80 default_server; 
    listen [::]:80 default_server ipv6only=on;

    root /home/myapp; 
    index index.html index.htm; 

    server_name myapp.com; 

    # redirect non-SSL to SSL
    location / {
        rewrite     ^ https://$server_name$request_uri? permanent;

   #serve static files
       location ~* "^/[a-z0-9]{40}\.(css|js)$" {
          root /home/myapp/build/bundle/programs/web.browser;
          access_log on;
          expires max;
        }
    #Serving favicons
        location ~ ^/(|favicon.ico|favicon.png|apple-touch-icon-precomposed.png) {
            root /home/myapp/www/favicons;
        }
    #Any requests with a URI starting with images
       location /images/  {
          root /home/streetfoodparty/www;
          access_log on;
         }
    }
}
# HTTPS server
server {
    listen 443 ssl spdy; # we enable SPDY here
    server_name streetfood.party; # this domain must match Common Name (CN) in the SSL certificate

    root html; # irrelevant
    index index.html; # irrelevant

    ssl_certificate /etc/nginx/ssl/myapp.pem; 
    ssl_certificate_key /etc/nginx/ssl/myapp.key; 

    # performance enhancement for SSL
    ssl_stapling on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 5m;

    # safety enhancement to SSL: make sure we actually use a safe cipher
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-A$

    # config to enable HSTS(HTTP Strict Transport Security) https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security
    # to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping
    add_header Strict-Transport-Security "max-age=31536000;";

    # If your application is not compatible with IE <= 10, this will redirect visitors to a page advising a browser update
    # This works because IE 11 does not present itself as MSIE anymore
    if ($http_user_agent ~ "MSIE" ) {
        return 303 https://browser-update.org/update.html;
    }

    # pass all requests to Meteor
    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade; # allow websockets
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Forwarded-For $remote_addr; # preserve client IP

        # this setting allows the browser to cache the application in a way compatible with Meteor
        # on every applicaiton update the name of CSS and JS file is different, so they can be cache infinitely (here: 30 days)
        # the root path (/) MUST NOT be cached
        if ($uri != '/') {
            expires 30d;
        }
    }
}

Nginx access.logs are telling me that the initial js and css ist served via nginx or do I also see it in there if it is served via meteor?
How do I know if something is server via nginx and not via meteor?

However www.myapp.com/images/example.png will not be served which is located at /home/myapp/www/images/example.png --> it loads my app with my Not Found site …

I’d like to serve all kind of images from the images folder - could anyone pleas tell me what I am doing wrong?

Hello @h2s20,

Have you seen this thread - Treat public folder by nginx instead of meteor
Should help you. Ping me if you need more info.

I had to move the location directive between the server blocks but according to my access.logs and my tests it’s now working.

The initial js and css files are served via nginx
The favicons are served via nginx
Static files are served from a special directory

Here is my working example

server_tokens off; # for security-by-obscurity: stop displaying nginx version

# this section is needed to proxy web-socket connections
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# HTTP
server {
    listen 80 default_server; # if this is not a default server, remove "defaul$
    listen [::]:80 default_server ipv6only=on;

    root /home/myapp/www/; # root is irrelevant
    index index.html index.htm; # this is also irrelevant

    server_name myapp.com; # the domain on which we want to host the app$

    # redirect non-SSL to SSL
    location / {
        rewrite     ^ https://$server_name$request_uri? permanent;

    #serve static files
       location ~* "^/[a-z0-9]{40}\.(css|js)$" {
          root /home/myapp/build/bundle/programs/web.browser;
          access_log on;
          expires max;
        }
    }
}
# HTTPS server
server {
    listen 443 ssl spdy; # we enable SPDY here
    server_name streetfood.party; # this domain must match Common Name (CN) in $

    root /home/myapp/www/;
    index index.html; # irrelevant

    ssl_certificate /etc/nginx/ssl/myapp.pem; # full path to SSL cert$
    ssl_certificate_key /etc/nginx/ssl/myapp.key; # full path to SSL $

    # performance enhancement for SSL
    ssl_stapling on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 5m;

    # safety enhancement to SSL: make sure we actually use a safe cipher
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDH$

    # config to enable HSTS(HTTP Strict Transport Security) https://developer.m$
    # to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_st$
    add_header Strict-Transport-Security "max-age=31536000;";

    # If your application is not compatible with IE <= 10, this will redirect v$
    # This works because IE 11 does not present itself as MSIE anymore
    if ($http_user_agent ~ "MSIE" ) {
        return 303 https://browser-update.org/update.html;
    }
  # check for existence or redirect to meteor
  location / {
    try_files $uri @application;
  }

   #Serving favicons
        location ~ ^/(favicon.ico|favicon.png) {
          root /home/myapp/www/static/favicons;
          access_log off;
         }

   #Any requests for with a URI starting with images
       location /static/  {
          access_log on;
       }
   # pass all requests to Meteor
    location @application {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade; # allow websockets
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Forwarded-For $remote_addr; # preserve client IP

        # this setting allows the browser to cache the application in a way com$
        # on every applicaiton update the name of CSS and JS file is different,$
        # the root path (/) MUST NOT be cached
        if ($uri != '/') {
            expires 30d;
        }
    }
}



Yeah that looks better. You were re-writing requests to https before you could serve them from nginx!

Does it make sense to put the location ~* “^/[a-z0-9]{40}.(css|js)$” in the location / { rewrite https…} block?
I did so but it does not make sense to me but the access.log tells me that xyz.css and xyz.js ist served via nginx however if i put the location ~* “^/[a-z0-9]{40}.(css|js)$” in the server {listen 443 …} block I have no xyz.css and xyz.js in the access.log which means that it is served by meteor - am I right?

This is how to handle HTTP -> HTTPS rewrite:

server {
  listen 80;
  listen [::]:80;
  server_name myapp.io;

  return 301 https://myapp.io;
}

Ah yeah, they should be in the https block

As for how to write the location blocks, try checking it with something like
https://detailyang.github.io/nginx-location-match-visible
Which visualises your rules and you can test urls against it

1 Like

Thank you for your hints!
This tool is very helpful - especially for learning and understanding nginx :slight_smile: