Nguyen-Blog
About
Published on

Setting up a server for Rails Applications

After purchasing a VPS for my Rails application, I spent half of day navigating through documentations, troubleshooting, googling, and chatting with AI to get everything configured. Along the way, I wrote down some valuable steps and insights. Hopefully, my note will save time for others, or even for me in the future.

The setup involves four main tasks:

  • Install Nginx
  • Set Up SSL Certificates with Let's Encrypt
  • Install Ruby
  • Install and Configure PostgreSQL

Note: these were my experiences with Debian 12 VPS

Installing Nginx

To ensure we use the latest version of Nginx, we can tell APT to retrieve packages from the official source instead of Debian's default repositories:

# Add Nginx’s official signing key
curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg > /dev/null

# Configure APT to use Nginx’s official package source
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian bookworm nginx" | sudo tee /etc/apt/sources.list.d/nginx.list

Next, install Nginx and enable it:

sudo apt update
sudo apt install nginx
sudo systemctl enable nginx
sudo systemctl start nginx

Useful Nginx Commands

nginx -v                # Check Nginx version  
nginx -t                # Validate Nginx configuration  
sudo less /var/log/nginx/access.log  # View access logs  
sudo tail -f /var/log/nginx/error.log  # Follow error logs in real-time  
sudo systemctl restart nginx  # Restart Nginx service  
journalctl -u nginx.service -f  # View logs with journalctl  

Setting Up SSL Certificates (Let's Encrypt)

To secure the application, first, map the domain using A records (@ and www) and update Nginx’s default server block:

# Verify domain ownership
nslookup example.com

# Set up web directory
sudo mkdir -p /var/www/example.com
echo "Hello World!" | sudo tee /var/www/example.com/index.html > /dev/null
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com

Modify /etc/nginx/conf.d/default.conf

server {
    listen 80;
    server_name example.com;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

restart nginx

sudo systemctl restart nginx

Install acme.sh to request Let's Encrypt for certificates. ACME.sh requires cron service which not installed on Debian by default

sudo apt update
sudo apt install -y cron
sudo systemctl enable cron
sudo systemctl start cron

Then install ACME.sh:

curl https://get.acme.sh | sh

Switch to staging evironment first. This is optional step, but I think we should do to avoid rate limits due to wrong configurations. We later can switch back to production if we feel confident about our configuration.

~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --staging
# switch back to production environment
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

Once ready, issue the SSL certificate:

~/.acme.sh/acme.sh --issue --keylength ec-256 --ecc -d example.com -d www.example.com --webroot /var/www/example.com --email test@example.com

Install the certificate:

mkdir -p /etc/nginx/ssl # create nginx ssl directory
~/.acme.sh/acme.sh --install-cert -d example.com \
    --key-file /etc/nginx/ssl/example.com.key \
    --fullchain-file /etc/nginx/ssl/example.com.cer \
    --reloadcmd "systemctl reload nginx"

Note:

  • There are two primary key lengths (RSA, ECC), each with a range of variations. For most use cases, the default key algorithm and length (ec-256) is recommended with acme.sh.
  • It's common to issue the cert for both domains (example.com and www.example.com), then configure web server (Nginx) to redirect one to the other. (www.example.com -> example.com)

Modify /etc/nginx/conf.d/default.conf to enable HTTPS and redirects:

# update /etc/nginx/conf.d/default.conf
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name example.com;
    ssl_certificate /etc/nginx/ssl/example.com.cer;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    
    root /var/www/example.com;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }
}

Enable automatic renewal

~/.acme.sh/acme.sh --cron

Use this command to view installed certificates

~/.acme.sh/acme.sh --list

Install Ruby

The simplest way to install Ruby is using rbenv. First, we install dependencies.

sudo apt update
sudo apt install -y git curl build-essential libssl-dev libreadline-dev zlib1g-dev libyaml-dev libffi-dev libgdbm-dev libncurses5-dev libdb-dev uuid-dev

Then install rbenv and ruby-build

# clone rbenv repository and add script to path
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc
exec "$SHELL"

# install ruby-build as plugins of rbenv
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

Install Ruby

rbenv install 3.4.3
rbenv global 3.4.3
ruby -v

Note: if we install rbenv from Debian's default repositories, Ruby will also be installed along with rbenv, but usually old version.

Setting Up PostgreSQL

To install PostgreSQL, ensure APT fetches packages from the official PostgreSQL repository:

# tell apt to install postgres from offical repository 
sudo apt install -y curl ca-certificates gnupg
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/postgresql.gpg
echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list

Install PostgreSQL

sudo apt update
sudo apt install -y postgresql-16

Log in postgresql terminal

sudo -i -u postgres # login to shell as postgres
psql

Create a database and user

CREATE ROLE rails_app_user WITH LOGIN PASSWORD 'StrongPa$$word123';
CREATE DATABASE rails_app_db;
GRANT ALL PRIVILEGES ON DATABASE rails_app_db TO rails_app_user;
SHOW hba_file; # find location of pg_hba.conf

Modify pg_hba.conf to allow local authentication

# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5
local   all             all                                     md5

Restart PostgreSQL for changes to affect

sudo systemctl restart postgresql

Tested connection

psql -U ails_app_user -d rails_app_db