By David Weldon

2013-08-01 20:14:50 8 Comments

The site configuration for my meteor app has directives which look like the following:

server {
  listen 443;
  server_name XXX;

  ssl on;
  ssl_certificate XXX;
  ssl_certificate_key XXX;

  location / {
    proxy_pass http://localhost:3000;
    proxy_set_header X-Real-IP $remote_addr;  #
    proxy_http_version 1.1;  # recommended for keep-alive connections per
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;

I feel like I should be telling nginx to serve contents of static_cacheable and setting the expires header to max. How exactly do I go about doing that? Are there other things I should add in here?


@Andrew Mao 2014-06-30 18:56:39

I made some updates and improvements to the other answer. Specifically,

  • the X-Forwarded-For header needs to be set for Meteor's new IP address detection that is done in this file. It does not appear that X-Real-IP is used.
  • the /nginx_status path can be used to monitor the amount of traffic coming through the proxy.

I've fiddled with this a bit and come up with the following configuration. Edit your fields appropriately.

First, compression, which speeds up load time considerably. Note that the gzip_buffers directive is usually automatically computed by default using the system's memory page size:

gzip on;                                                                                                                                                  
gzip_disable "msie6";                                                                                                                                     
gzip_min_length 1100;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

The server config itself:

server {
    listen 443 ssl;

    ssl on;
    ssl_certificate /etc/ssl/nginx/certificate.crt;
    ssl_certificate_key /etc/ssl/nginx/certificate.key;

    access_log /var/log/nginx/localhost.ssl_access_log main;
    error_log /var/log/nginx/localhost.ssl_error_log info;

    # Forward to meteor server                                                                                                                        
    location / {
         proxy_pass http://localhost:3000;
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection "upgrade";
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # copied from
    location /nginx_status {
         stub_status on;
         access_log off;
         deny all;

Finally, as Dan mentioned, you will need to set the HTTP_FORWARDED_COUNT environment variable in Meteor to properly pick up the client IPs from behind the reverse proxy.

@David Weldon 2014-06-30 19:21:01

Thanks Andrew. Another good resource I found is h5bp/server-configs-nginx.

@Dan Dascalescu 2015-07-20 09:16:10

Thanks for the update; deleted my comment. I've also tested X-Real-IP and X-Forwarded-For, and only the latter is read by Meteor. As expected based on the livedata_server code.

@Liko 2016-05-17 20:15:51

Env var HTTP_FORWARDED_COUNT is still required? is up to date?

@David Weldon 2013-08-04 03:56:50

Although I'm not an nginx expert, I feel like I have a much better understanding of how to do this now. As I figure out more I'll update this answer.

One possible solution to my original question is this:

location ~* "^/[a-z0-9]{40}\.(css|js)$" {
  root /home/ubuntu/app/bundle/programs/web.browser;
  access_log off;
  expires max;

Which says: Any URL for this site containing a slash followed by 40 alphanumeric characters + .js or .css, can be found in the web.browser directory. Serve these files statically, don't write them to the access log, and tell the client that they can be cached forever.

Because the the main css and js files are uniquely named after each bundle operation, this should be safe to do.

I'll maintain a full version of this example here. It's also worth noting that I'm using a recent build of nginx which supports WebSockets as talked about here.

Finally, don't forget to fully enable gzip in your nginx config. My gzip section looks like:

gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

After doing all that, I was able to get a decent score on pagespeed.

update 9/17/2014:

Updated the paths for meteor

@Nyxynyx 2013-12-16 00:58:00

If you allow caching of all css/js files, will the user's copy still be updated (like a hot code reload) when u make a change to the Meteor files in the client directory?

@David Weldon 2013-12-16 01:02:08

Yes. Every time you bundle your app it will create a new pair of css and js files with unique names.

@deepwell 2013-12-16 01:52:38

How do you handle keeping websocket connections alive when deploying with live users on the site? I get 502 Bad Gateway errors and current users have to reload the app.

@David Weldon 2013-12-16 03:28:34

I haven't seen that issue when deploying - I just write over the old bundle with the new bundle and then do a forever restart.

@Andrew Mao 2014-01-29 22:09:40

+1 Thanks for posting. Does this still work if not running Meteor's bundle but just the development server? @deepwell are you getting the 502 errors over hot code reload? What happens over hot code reloads for css/js files? What is the purpose of gzipping?

@David Weldon 2014-01-29 22:19:55

@AndrewMao This config assumes the app has been bundled. You'd need to modify the paths and remove the expires header to get this to work in dev mode, but it should be possible.

@Andrew Mao 2014-01-30 00:41:48

@DavidWeldon Thanks for the response. Actually both gzip and websocket proxying seem to work with normal meteor run, and the only thing to leave out is the static files. Hot code reload works across the reverse proxy as well, in my initial tests. You may want to add the gzip config to your gist.

@David Weldon 2014-01-30 18:40:35

@AndrewMao Nice - glad you got it working. I didn't add the gzip settings to the gist because they really belong in a different section of your main nginx.conf rather than in a site config. I think the real solution here if for me to blog about how I do this in production. When I do that I'll add a link from the answer.

@Jimmy M.G. Lim 2014-03-17 21:05:27

I would suggest adding jpg or png resources as well? if they are served from the same instance.

@Dan Dascalescu 2015-03-10 02:21:36

@Stephan has done some benchmarking showing that Nginx can serve static assets about twice as fast as Node.js.

Related Questions

Sponsored Content

11 Answered Questions

[SOLVED] Node.js + Nginx - What now?

  • 2011-02-15 20:49:02
  • Van Coding
  • 331703 View
  • 941 Score
  • 11 Answer
  • Tags:   node.js nginx concept

2 Answered Questions

Keycloak Redirect url with nginx is going to http rather than https

  • 2018-04-03 12:02:15
  • Atulya Nair
  • 1184 View
  • 3 Score
  • 2 Answer
  • Tags:   nginx jboss

0 Answered Questions

nginx docker compose redirect delay

2 Answered Questions

2 Answered Questions

[SOLVED] Express - req.ip returns

2 Answered Questions

[SOLVED] Configuring HTTPS for Express and Nginx

1 Answered Questions

[SOLVED] AWS EB - Redirect all traffic to https

1 Answered Questions

Wordpress constant redirect with nginx upstream

1 Answered Questions

[SOLVED] Ngnix Jsession changed redirection issue

  • 2015-06-30 11:09:34
  • Mukesh Kumar
  • 437 View
  • 2 Score
  • 1 Answer
  • Tags:   nginx

2 Answered Questions

Sponsored Content