Skip to content

Setting up Ruby on Rails with Passenger + Nginx in a CentOS 7 VM running on Google Cloud Platform


All commands are written (unless explicitly stated) from the perspective of a non-root user with sudo permissions. The intent is to create a user which will run the application we are creating, but that user will not have sudo permissions.

Machine Setup

I started here:

The only real difference was that I select the CentOS 7 OS.

Tool Setup

From here:

sudo yum install -y curl gpg gcc gcc-c++ make

Install RVM:

sudo yum -y install tar which sudo yum -y install patch libyaml-devel libffi-devel glibc-headers autoconf gcc-c++ glibc-devel readline-devel zlib-devel openssl-devel bzip2 automake libtool bison
sudo gpg --keyserver hkp:// --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL | sudo bash -s stable 
sudo usermod -a -G rvm `whoami`

Setup RVM secure_path:

if sudo grep -q secure_path /etc/sudoers; then sudo sh -c "echo export rvmsudo_secure_path=1 >> /etc/profile.d/" && echo Environment variable installed; fi

Start a new shell and install the latest ruby and bundler:

rvm install ruby
rvm --default use ruby
#Using /home/nik/.rvm/gems/ruby-2.6.3
gem install bundler

Then install Node.js for Ruby on Rails

sudo yum install -y epel-release
sudo yum install -y --enablerepo=epel nodejs npm

Nginx+Passenger+Ruby Setup

Then to get nginx + ruby running I followed directions here:

sudo yum install -y epel-release yum-utils
sudo yum-config-manager --enable epel
sudo yum clean all && sudo yum update -y

Date configuration:

# if the output of date is wrong, please follow these instructions to install ntp
sudo yum install -y ntp
sudo chkconfig ntpd on
sudo ntpdate
sudo service ntpd start

Install Passenger+Nginx

sudo yum install -y pygpgme curl
sudo curl --fail -sSLo /etc/yum.repos.d/passenger.repo
sudo yum install -y nginx passenger || sudo yum-config-manager --enable cr && sudo yum install -y nginx passenger

Uncomment the following settings in /etc/nginx/conf.d/passenger.conf:

passenger_root /some-filename/locations.ini;
passenger_ruby /usr/bin/ruby;
passenger_instance_registry_dir /var/run/passenger-instreg;

Edit the file and then restart the service:

sudo vi /etc/nginx/conf.d/passenger.conf
sudo service nginx restart
sudo /usr/bin/passenger-config validate-install
sudo /usr/sbin/passenger-memory-stats

User Setup

We should not run our app as root because we will give too much power to the app if it gets hacked, and we should not run it as our user, because we may want different setups for different apps, which may all be running on the same instance.

sudo adduser $NEWUSER
sudo mkdir -p ~$NEWUSER/.ssh 
touch $HOME/.ssh/authorized_keys
sudo sh -c "cat $HOME/.ssh/authorized_keys >> ~$NEWUSER/.ssh/authorized_keys" 
sudo chown -R $NEWUSER: ~$NEWUSER/.ssh 
sudo chmod 700 ~$NEWUSER/.ssh 
sudo sh -c "chmod 600 ~$NEWUSER/.ssh/*"
sudo usermod -a -G rvm $NEWUSER

Git Project Setup

Setup the project “deploy to” directory:

sudo yum install -y git
sudo mkdir -p /var/www/appname
sudo chown $NEWUSER: /var/www/appname

Create a project on your SCM service site (GitHub, BitBucket, etc.).

Then setup ssh so you can clone.

sudo su $NEWUSER
ssh-keygen -t rsa
cat ~/.ssh/

Then add the ssh key to the service.

cd /var/www/appname
git clone git@<service>.com:<user>/myproject.git ./

I just copied the example, then moved all of that into my repo:

git clone --bare ./

Run the Project

From here:

rvm use ruby-2.6.3
#Using /usr/local/rvm/gems/ruby-2.6.3

Now install all of the gems:

bundle install --deployment --without development test

Generate the secret for Rails

bundle exec rake secret

Put that output into: config/secrets.yml

chmod 700 config db 
chmod 600 config/database.yml config/secrets.yml

Now create the DB and tables:

bundle exec rake assets:precompile db:migrate RAILS_ENV=production

This produced:

rails aborted!
LoadError: Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile.

Caused by:
Gem::LoadError: can't activate sqlite3 (~> 1.3.6), already activated sqlite3-1.4.0. Make sure all dependencies are added to Gemfile.

Looks like the Gemfile didn’t specify the dependency for ActiveRecord of < 1.4 and as such mine installed the sqlite3 version of 1.4.0 which is not compatible.

An attempt to add the following to the Gemfile:

gem 'sqlite3', '~> 1.3', '< 1.4'

Will cause:

bundle exec rake assets:precompile db:migrate RAILS_ENV=production

We are testing out a production environment. The normal process is to modify and change this stuff on a development machine and submit the changes for both the Gemfile and the Gemfile.lock to the SCM repo and as such when the production server receives the changes it will get them synchronized.

Because we are testing our setup on a production server for now, we will disable this feature, but we must re-enable it before actually going to production!

To do this we will unset the frozen configuration, but first let’s see how our machine is setup so we can restore it later:

$ bundle config frozen
Settings for `frozen` in order of priority. The top value will be used
Set for your local app (/var/www/nitrogen/.bundle/config): true
Set for the current user (/home/nitrogen/.bundle/config): false

So for now:

bundle config --local frozen false
bundle install --deployment --without development test
#Installing sqlite3 1.3.13 (was 1.4.0) with native extensions
passenger-config about ruby-command

Now update the config file:

  • /etc/nginx/conf.d/myapp.conf
server {
    listen 80;

    # Tell Nginx and Passenger where your app's 'public' directory is
    root /var/www/myapp/code/public;

    # Turn on Passenger
    passenger_enabled on;
    passenger_ruby /path-to-ruby;

Domain Setup

In GCP under Networking > VPC Network > External IP Addresses > change it to Static

This IP was automatically assigned to my VM. I was able to go to that IP address and see my nginx configuration page.

I then went to my domain host and created an A record to this static IP.

My domain, then resolved to the correct IP address.

Now when I visit the domain instead of seeing the nginx configuration page I see:

500 Internal Server Error

This is because when you connect from the domain it knows and tries to load that site (from the configuration file we just updated). However, while loading the app we hit an error, so let’s see what that was:

sudo tail /var/log/nginx/access.log

Hmmm looks like a permissions issue:

2019/08/07 12:34:00 [alert] 25410#0: *34 Cannot stat '/var/log/nginx/access.log': Permission denied (errno=13); This error means that the Nginx worker process (PID 54321, running as UID 987) does not have permission to access this file. Please read this page to learn how to fix this problem:

To verify which user this service is running as (987):

$ grep 987 /etc/passwd
nginx:x:987:876:Nginx web server:/var/lib/nginx:/sbin/nologin

Looking at permissions, they look fine:

$ namei -l /var/www/myapp/code/
f: /var/www/myapp/code/
dr-xr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-xr-x myappuser myappuser myapp
drwxr-xr-x myappuser myappuser code
-rw-rw-r-- myappuser myappuser

It looks like nginx should be able to read the file, just to make sure..

sudo -u nginx tail /var/www/myapp/code/

This works fine so wtf…

Well it looks like Security Enhanced Linux strikes again. To fix this:

chcon -Rt httpd_sys_content_t /var/www/myapp/code


Hello world!

Congratulations, you are running this app in Passenger!

TheSoftwareProgrammer View All

I like science and writing software.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: