Deploying a Meteor app with Nginx from scratch

Just like ways of skinning a cat (whatever that is supposed to mean), there are many ways to deploy a Meteor application. One way is to bundle the Meteor application so that it can be run by Node. The problem with this is that it isn’t wise to run an application like Meteor through your public port (which is 80). So it is considered best practice to use a cross-proxy and redirect public requests with Nginx or another service. In this blog post, we’ll look at how to set up the configuration and deploy a Meteor app with Nginx. There are other resources online for this, but many of them are outdated, so I feel that it will be useful.

  1. Log into remote server and create a user

First you will want to ssh into your virtual machine (Digital Ocean, Azure, etc.). If you are new to logging in to remote servers, this could be a handy review. The command looks something like ssh root@IP_ADDRESS.

From here, you will want to add a user, since running commands as root is usually not recommended. To do this, you can type adduser USERNAME. Then to add sudo privileges to the user, you can type visudo and make the following changes:

under the line root ALL=(ALL:ALL) ALL add

newuser ALL=(ALL:ALL) ALL

Once you establish the username and password, you will be able to sign in as the new user and make changes with root privileges.

2. Download the latest Meteor distribution

This is a simple curl command, as explained on the official Meteor site:

curl | sh

This will download Meteor to your machine. You can test that it works by calling meteor — version and seeing that it gives a response

3. Install Node

Currently Meteor only supports older versions of Node. They are working on this in the Meteor roadmap. For now, we will use Node v10.04. Here are the commands:

curl -sL | sudo -E bash -
sudo apt-get install -y build-essential nodejs

You can then make sure you have global access to npm and node

4. Install Forever

We use forever to run our Meteor instance as a continuous program in the background.

sudo npm install -g forever

If you run forever list, you then should see something like this

5. Install Git and Clone Repository

We need to install git to clone our Meteor repository.

sudo apt-get install git 
git clone YOUR_REPO_NAME

6. Check that the app runs locally

cd into your app directory and run it locally — npm install and then meteor run

6. Install Nginx

We use Nginx to route incoming requests to port 80 to our Meteor port (for us, 8080). This is the complex part of deployment that can be tricky at first. We’ll try to make this as easy as possible through the instructions.

As per the nginx docs, we need to first create a signing key for nginx. You can download the signing key from the above link to your computer. It should look something like this:

In the home directory of your remote server, you’ll want to create a file nginx_signing.key and copy the contents to it. To verify the signing key, type sudo apt-key add nginx_signing.key. You should see an OK response.

Next you’ll need the “codename” for your virtual server. If you are using Ubuntu 14.04, it is “trusty”. A list of different codenames can be found here.

Using the codename, we open up the file /etc/apt/sources.list and append these 2 lines to the bottom with the proper codename

deb codename nginx
deb-src codename nginx

Finally, we can install the nginx package

sudo apt-get update
sudo apt-get install nginx

7. Configure Nginx to process incoming requests

Next we’ll need to add our nginx file that processes requests on the publicly-accessible port 80. We can go to the directory /etc/nginx where all the work will happen. If you check the available files (ls), you should see something like this:

We need to create 2 directories: “sites-available” and “sites-enabled”. We will then create a file “sites-available/app” and edit it with our server configuration:

# this section is needed to proxy web-socket connections
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

location = /favicon.ico {
root /home/USERNAME/portal/programs/web.browser/app;
access_log off;

location ~* "^/[a-z0-9]{40}\.(css|js)$" {
gzip_static on;
root /home/USERNAME/portal/programs/web.browser;
access_log off;

location ~ "^/packages" {
root /home/USERNAME/portal/programs/web.browser;
access_log off;

# pass requests to Meteor
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; #for websockets
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;

Save this file and then create a symbolic link to a file with the same name in “sites/enabled”.

sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app

Check that the symbolic link worked — cat /etc/nginx/sites-enabled/app. You should see the same code from above.

Last, add this to the bottom of the /etc/nginx/nginx.conf file to include your changes:

include /etc/nginx/sites-enabled/*;

Great! Now nginx is configured to pass all requests to our server to our Meteor instance on port 8080. Now we just have to boot up the Meteor app and run it in the background.

8. Run the Meteor app on port 8080 through Forever

First start nginx

sudo nginx -t 

Later, we can use the command sudo service nginx restart to reload the configuration. If you go to your server IP address in your browser, you should see something like this:

Next we will want a script to load our environment variables and build the Meteor bundle. Assuming we have our environment variables (MONGO_URL, ROOT_URL, etc.) in a file “” one directory up, here is one we can use:

#load environment variables
source ../
meteor update --release 1.3.1 #ensure proper version of Meteor
npm install # install NPM dependencies
npm prune --production # remove development dependencies
rm -rf ~/bundle # remove the previous bundle
meteor build --directory ~ # build the current bundle 
cd ~/bundle/programs/server # enter the bundle
npm install # install dependencies
mv ~/bundle ~/portal 
# make sure the logs directory exists
mkdir ~/logs
# use forever to restart the Node.js server
export PORT=8080
cd ~/portal
forever stop main.js
forever start -a -l ~/logs/forever.log -o ~/logs/portal.out -e ~/logs/portal.err main.js

If we go to the address of our IP, our app should be running!

I hope this blog post was helpful. There is a lot more you can do with Nginx, but this is a very common use-case. Please press the like button if you enjoyed the article, and follow me @tomgoldenberg on Twitter.