Skip to main content

Run your Meteor App on a Production Ubuntu 16.04 Server with Nginx

Introduction

Meteor enables developers to create apps and quickly test them on a development webserver. Once you have created your app the big question is how to run it on a production server. In this tutorial we will demostrate how to run your Meteor app on Ubuntu 16.04 using Nginx

Credits and Acknowledgements

This entire post is based off this Digital Ocean article. The article was modified for issues we encountered and adapted for Ubuntu 16.04. The entire credit goes to Daniel Speichert

Objective

In this tutorial we will:

  • Install and Configure Nginx with HTTPS enabled
  • Install MongoDB
  • Install NodeJS
  • Bundle your Meteor App
  • Create a startup script to automatically start your app on reboot

Assumptions

We assumue the following:

  • You already have a fresh install of Ubuntu 16.04 Server
  • SSH enabled on your fresh install
  • You have root privelages on your server
  • You have a different server where you can insall meteor and budle your app (You could possibly do it on the same server but we haven't verfied it)
  • Basic familiarity with using the Linux command line (terminal)
  • We will be installing a Personal Budget App in production. The source code of this app is available here. Replace the word budget with the name of you app
  • Our Production server has an IP of 192.168.1.1
  • Our development server on which the Meteor App was developed has an IP of 192.168.1.2

Installation

This section covers the installation and configuration of various software components required to get the Meteor app running in production

Step 1 - SSH in to your server and get root access


 ssh ubuntu@192.168.1.1
 sudo su
 apt-get update

Step 2 - Install and Configure Nginx

 apt-get install -y nginx 
Create a the following file /etc/nginx/sites-available/budget
 vim /etc/nginx/sites-available/budget 
Cut and paste following contents in the file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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 "default_server"
    listen [::]:80 default_server ipv6only=on;

    root /usr/share/nginx/html; # root is irrelevant
    index index.html index.htm; # this is also irrelevant

    server_name 192.168.1.1; # the domain on which we want to host the application. Since we set "default_server" previously, nginx will answer all hosts anyway.

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

# HTTPS server
server {
    listen 443 ssl spdy; # we enable SPDY here
    server_name 192.168.1.1; # this domain must match Common Name (CN) in the SSL certificate

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

    ssl_certificate /etc/nginx/ssl/budget.pem; # full path to SSL certificate and CA certificate concatenated together
    ssl_certificate_key /etc/nginx/ssl/budget.key; # full path to SSL 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-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK';

    # 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://0.0.0.0:8080;
        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;
        }
    }
}

Step 3 - Create Keys using OpenSSL


1
2
3
4
5
6
7
8
mkdir /etc/nginx/ssl
chmod 0700 /etc/nginx/ssl
cd /etc/nginx/ssl/
openssl genrsa -des3 -out budget.key 2048
openssl req -new -key budget.key -out budget.csr
cp budget.key budget.key.org
openssl rsa -in budget.key.org -out budget.key
openssl x509 -req -days 365 -in budget.csr -signkey budget.key -out budget.pem

Step 4 - Verify Nginx Install


1
2
rm /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/budget /etc/nginx/sites-enabled/budget
Verify the Ngix configuration using the following commands:

1
2
nginx -t 
nginx -s reload 
You should now be able to navigate to https://your-server-ip/. You will see a 502 Bad Gateway message which is exptected at this time

Step 5 - Install MongoDB


1
2
apt-get install -y mongodb-server
netstat -ln | grep -E '27017|28017'
Expected output of the netstat command is:

1
2
tcp        0      0 127.0.0.1:27017         0.0.0.0:*               LISTEN
unix  2      [ ACC ]     STREAM     LISTENING     13178    /tmp/mongodb-27017.sock

Step 6 - Install NodeJS


1
2
curl -sL https://deb.nodesource.com/setup_4.x | bash -
apt-get install -y nodejs

Step 7 - Create a System User


1
adduser --disabled-login budget 

Step 8 - Install Upstart and create the upstart script


1
apt-get install upstart upstart-sysv
Create a the file /etc/init/budget.conf. The autor of this file is Daniel Speichert

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# upstart service file at /etc/init/budget .conf
description "Meteor.js (NodeJS) application"
author "Daniel Speichert "

# When to start the service
start on started mongodb and runlevel [2345]

# When to stop the service
stop on shutdown

# Automatically restart process if crashed
respawn
respawn limit 10 5

# we don't use buil-in log because we use a script below
# console log

# drop root proviliges and switch to mymetorapp user
setuid budget
setgid budget

script
export PATH=/opt/local/bin:/opt/local/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export NODE_PATH=/usr/lib/nodejs:/usr/lib/node_modules:/usr/share/javascript
# set to home directory of the user Meteor will be running as
export PWD=/home/budget
export HOME=/home/budget
# leave as 127.0.0.1 for security
export BIND_IP=127.0.0.1
# the port nginx is proxying requests to
export PORT=8080
# this allows Meteor to figure out correct IP address of visitors
export HTTP_FORWARDED_COUNT=1
# MongoDB connection string using budget as database name
export MONGO_URL=mongodb://localhost:27017/budget
# The domain name as configured previously as server_name in nginx
export ROOT_URL=https://192.168.1.1
# optional JSON config - the contents of file specified by passing "--settings" parameter to meteor command in development mode
export METEOR_SETTINGS='{ "somesetting": "someval", "public": { "othersetting": "anothervalue" } }'
# this is optional: http://docs.meteor.com/#email
# commented out will default to no email being sent
# you must register with MailGun to have a username and password there
# export MAIL_URL=smtp://postmaster@mymetorapp.net:password123@smtp.mailgun.org
# alternatively install "apt-get install default-mta" and uncomment:
# export MAIL_URL=smtp://localhost
exec node /home/budget/bundle/main.js >> /home/budget/budget.log
end script

Step 9 - Bundle your App - We are performing this step on a different Ubuntu 16.04 server

If you have your own app you can skip using the budget app
Install Meteor

1
curl https://install.meteor.com/ | sh
Install Git

1
apt-get install git
Download the Budget App

1
2
3
4
mkdir personal-budget
cd personal-budget/
git init
git pull https://github.com/mehrashiv/personal-budget.git
Verify the app is working

1
meteor
Navigate to your browser e.g. http://192.168.1.2:3000 and ensure your app is up and running
Build your App

1
meteor build .
Copy your app to your production server

1
scp personal-budget.tar.gz ubuntu@192.168.1.1:/home/ubuntu

Step 10 - Setup your App

Drop in to the budget user directory and copy your app
1
2
cd /home/budget
cp /home/ubuntu/personal-budget.tar.gz .
Unzip your App
1
tar -zxf personal-budget.tar.gz
Install some required packages
1
apt-get install -y g++ make python
NPM Install
1
2
3
4
cd /home/budget/bundle/programs/server/
npm install
npm install bcrypt
reboot 
After reboot Delete the bcrypt folder and restart your app
1
2
3
4
sudo su
cd /home/budget/bundle/programs/server/npm/node_modules/meteor/
rm -rf npm-bcrypt/
start budget
Your App should be running on https://192.168.1.1




Comments

  1. Hey can you explain me how to do it on a http server and host 2 or more apps in the same server ?

    ReplyDelete
  2. Hi Oscar,
    I had started exploring that, but rather than run everything on one server, I have started using docker which is light weight and will enable us to run a container for each application.

    ReplyDelete
  3. Thank you so much! I've been looking for a tutorial like this for a long time.

    ReplyDelete

Post a Comment

Popular posts from this blog

Authenticating Traefik Apps with Authentik

 Introduction In our previous post , we secured the Homer app with trusted Let's Encrypt certificates using Traefik as a reverse proxy. But what if only authorized users should access Homer? In this blog, we'll address this by adding multi-factor authentication to Homer using Authentik as an Identity Provider (IdP). Objective The core objectives of this tutorial are to: Set Up Secure Access with Authentik:  Install Authentik using Docker Compose and create your first user to manage access control. Secure Homer with Authentik:  Configure Authentik to act as a gatekeeper, ensuring only authorized users can access your Homer application. Simplify Logins with Traefik:  Integrate Traefik with Authentik to enable Single Sign-On (SSO) for a seamless login experience across your applications. Connect Homer to Authentik:  Configure Homer to leverage Authentik's authentication system for secure logins. Topology For the topology details please see the previous post ....

Traefik Install and configuration - Part 1

Introduction In this tutorial we are going to install and configure Traefik Proxy . As quoted on their website "Traefik is a leading modern reverse proxy and load balancer that makes deploying microservices easy. Traefik integrates with your existing infrastructure components and configures itself automatically and dynamically." All our applications will be front-ended by traefik. Objective The core objective for this tutorial is to: Configure an A record in our DNS server (pfsesne firewall) to point to our traefik proxy Configure and install Traefik in docker Configure and install two http based applications ( homer and heimdall ) in docker that will be accessed via traefik Topology The above image represents our topology. Our pfsense firewall is connected directly to the Internet and is NAT'ing all traffic from the internal network to the outside world. We have a single LAN segment (192.168.11.0/24) defined on pfsense and we have the following two PC's connected d...