Skip to content

Webserver and SSL Setup

Jonas Breder edited this page Sep 3, 2026 · 1 revision

Web Server & SSL/TLS Configuration (Bare-Metal)

This guide provides comprehensive instructions for deploying hroot on a Linux server without Docker, configuring a production web server (NGINX or Apache), provisioning SSL/TLS certificates (Let's Encrypt or Institutional / Custom Certificates), and setting up automated renewal.


Architecture Overview

In a bare-metal environment, Ruby on Rails runs with an application server (typically Puma or Phusion Passenger). A production-grade reverse proxy (such as NGINX or Apache) sits in front of the application server to:

  1. Terminate SSL/TLS encryption (HTTPS on port 443).
  2. Redirect unencrypted HTTP traffic (port 80) to HTTPS.
  3. Serve static assets efficiently.
  4. Manage client connections, timeouts, and file upload limits.
[ Web Browsers ] ─── HTTPS (Port 443) ───► [ NGINX / Apache ] ─── HTTP (Port 3000 / Socket) ───► [ Puma / Rails ]

Option A: NGINX Reverse Proxy + Puma (Recommended)

NGINX paired with Rails' built-in Puma application server is the modern standard for deploying Rails applications.

1. Install NGINX

On Ubuntu / Debian:

sudo apt update
sudo apt install -y nginx

2. Configure Puma as a Systemd Service

Create a systemd unit file to manage the Puma background process automatically:

sudo nano /etc/systemd/system/hroot.service

Paste the following service definition (adjust /home/hroot/hroot and username hroot to match your installation path):

[Unit]
Description=HROOT Rails Application Server (Puma)
After=network.target mysql.service

[Service]
Type=simple
User=hroot
WorkingDirectory=/home/hroot/hroot
Environment=RAILS_ENV=production
# If using rbenv, ensure the PATH points to the rbenv shims:
Environment="PATH=/home/hroot/.rbenv/shims:/home/hroot/.rbenv/bin:/usr/local/bin:/usr/bin:/bin"

ExecStart=/home/hroot/.rbenv/shims/bundle exec puma -C config/puma.rb -b tcp://127.0.0.1:3000
Restart=always
RestartSec=5

# Resource limits
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now hroot
sudo systemctl status hroot

3. Configure NGINX Site

Create a dedicated NGINX configuration file for your hroot domain:

sudo nano /etc/nginx/sites-available/hroot.conf

Insert the following configuration:

# Upstream Puma application server
upstream hroot_puma {
    server 127.0.0.1:3000 fail_timeout=0;
}

# HTTP Server: Redirect all traffic to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name hroot.example.org;

    # Let's Encrypt ACME challenge location
    location /.well-known/acme-challenge/ {
        root /var/www/html;
        allow all;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS Server: SSL Termination and Reverse Proxy
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name hroot.example.org;

    # Path to static public files
    root /home/hroot/hroot/public;

    # SSL Certificate files (configured manually or via Certbot)
    ssl_certificate /etc/ssl/certs/hroot_fullchain.pem;
    ssl_certificate_key /etc/ssl/private/hroot_privkey.pem;

    # Modern TLS Security Parameters
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # Security Headers
    add_header X-Frame-Options SAMEORIGIN always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # Max upload body size for participant attachments & imports
    client_max_body_size 50M;

    # Serve precompiled static assets directly with long cache headers
    location ~ ^/(assets|packs)/ {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
    }

    # ActionCable / WebSocket support (for live updates & waiting rooms)
    location /cable {
        proxy_pass http://hroot_puma;
        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;
        proxy_set_header X-Forwarded-Proto https;
    }

    # Proxy all dynamic requests to Puma
    location / {
        proxy_pass http://hroot_puma;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port 443;
        proxy_redirect off;
    }

    error_page 500 502 503 504 /500.html;
}

Enable the configuration and reload NGINX:

sudo ln -sf /etc/nginx/sites-available/hroot.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Option B: Apache 2.4 + Phusion Passenger

If your university or organization requires Apache, use Phusion Passenger with mod_ssl.

1. Install Apache & Passenger

Follow the official Passenger APT repository instructions:

sudo apt install -y apache2 libapache2-mod-passenger
sudo a2enmod ssl headers rewrite

2. Configure Apache VirtualHosts

Create /etc/apache2/sites-available/hroot.conf:

# Port 80: Redirect to HTTPS
<VirtualHost *:80>
    ServerName hroot.example.org
    ServerAdmin admin@example.org

    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>

# Port 443: SSL and Passenger Execution
<VirtualHost *:443>
    ServerName hroot.example.org
    ServerAdmin admin@example.org

    DocumentRoot /home/hroot/hroot/public

    <Directory /home/hroot/hroot/public>
        AllowOverride all
        Options -MultiViews
        Require all granted
    </Directory>

    # Passenger Rails Settings
    PassengerEnabled on
    PassengerAppEnv production
    PassengerRuby /home/hroot/.rbenv/shims/ruby

    # SSL / TLS Settings
    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/hroot_fullchain.pem
    SSLCertificateKeyFile /etc/ssl/private/hroot_privkey.pem

    # Security Headers
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    Header always set X-Content-Type-Options nosniff
    Header always set X-Frame-Options SAMEORIGIN
</VirtualHost>

Enable the site and reload Apache:

sudo a2ensite hroot.conf
sudo apache2ctl configtest
sudo systemctl restart apache2

SSL Certificate Provisioning

Method 1: Automated Free Certificates via Let's Encrypt (Certbot)

Let's Encrypt provides free, trusted, automatically renewed SSL certificates.

1. Install Certbot

On Ubuntu / Debian:

sudo apt update
# For NGINX:
sudo apt install -y certbot python3-certbot-nginx

# For Apache:
sudo apt install -y certbot python3-certbot-apache

2. Obtain and Install Certificate

Run Certbot interactively:

  • For NGINX:
    sudo certbot --nginx -d hroot.example.org
  • For Apache:
    sudo certbot --apache -d hroot.example.org

Certbot will automatically verify your domain via HTTP-01 challenge, retrieve the certificate, update your webserver configuration, and test syntax.

3. Automated Renewal Verification

Certbot automatically installs a systemd timer that checks certificate validity twice daily.

To verify the timer is active:

systemctl status certbot.timer

To test that renewal works seamlessly without waiting for expiration:

sudo certbot renew --dry-run

Tip

If you need to reload your web server automatically when a certificate is renewed, Certbot supports renewal hooks:

# Place a post-renewal hook script:
echo '#!/bin/sh' | sudo tee /etc/letsencrypt/renewal-hooks/post/reload-webserver.sh
echo 'systemctl reload nginx || systemctl reload apache2' | sudo tee -a /etc/letsencrypt/renewal-hooks/post/reload-webserver.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/post/reload-webserver.sh

Method 2: Institutional / Custom Certificates (e.g. DFN-PKI, GÉANT, Sectigo)

Many universities and research institutes issue official organization-validated certificates through institutional PKIs.

1. Prepare Certificate Files

You will typically receive:

  • Server Certificate: certificate.crt
  • Intermediate CA Bundle: chain.crt
  • Private Key: Generated when creating the CSR (e.g., server.key)

Combine the server certificate and intermediate bundle into a single fullchain.pem:

cat certificate.crt chain.crt > /etc/ssl/certs/hroot_fullchain.pem

Move the private key to the secured private directory:

sudo cp server.key /etc/ssl/private/hroot_privkey.pem
sudo chmod 600 /etc/ssl/private/hroot_privkey.pem
sudo chown root:root /etc/ssl/private/hroot_privkey.pem

2. Configure Web Server

  • In NGINX:
    ssl_certificate /etc/ssl/certs/hroot_fullchain.pem;
    ssl_certificate_key /etc/ssl/private/hroot_privkey.pem;
  • In Apache:
    SSLCertificateFile /etc/ssl/certs/hroot_fullchain.pem
    SSLCertificateKeyFile /etc/ssl/private/hroot_privkey.pem

Rails Application Configuration (FORCE_SSL)

Once SSL is configured on your reverse proxy, inform the Rails application to enforce HTTPS cookies and HTTP Strict Transport Security (HSTS).

In your .env file (/home/hroot/hroot/.env):

FORCE_SSL=true

When FORCE_SSL=true:

  • Cookies are sent exclusively over encrypted connections (Secure flag).
  • HSTS (Strict-Transport-Security) headers prevent downgrade attacks.
  • Internal redirects ensure all URLs generated by the app use https://.

After modifying .env, restart Puma or Passenger:

# Puma:
sudo systemctl restart hroot

# Passenger:
sudo passenger-config restart-app /home/hroot/hroot

Verification & Troubleshooting

1. Test HTTPS Headers from Terminal

curl -I https://hroot.example.org

Expected response headers:

HTTP/2 200
strict-transport-security: max-age=63072000; includeSubDomains; preload
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff

2. Verify Port Forwarding

Verify that Puma is listening only on 127.0.0.1:3000 (not accessible directly from the public internet):

ss -tuln | grep 3000

Output should be: 127.0.0.1:3000.

3. SSL Expiration Check

Check certificate validity date from the command line:

openssl s_client -connect hroot.example.org:443 -servername hroot.example.org 2>/dev/null | openssl x509 -noout -dates

HROOT - Hamburg Registration and Organization Online Tool

  1. Installation

    1. System Requirements and Prerequisites

    2. Docker (precompiled)

    3. Docker (self-compiled)

    4. Manual Deployment

    5. First steps after installation

    6. Environment Configuration

    7. Backup and Maintenance

    8. Starting a local server and running automated tests

    9. Basic configuration

    10. Language configuration and timezone

    11. Custom field definitions

  2. Updating an existing installation

  3. Overview

    1. Roles: Users, Admins, Experimenters, Banking‐Administrators
    2. Experiments, Sessions, session types
  4. Subject pool and user management

    1. Browsing the pool
    2. Exporting data
    3. Adding users
    4. Deleting users
    5. Finding duplicate users
    6. More user management
  5. Experiment management

    1. Managing Experiments
    2. Adding Sessions
    3. Experiment and Session Inheritance
    4. Reserving locations (booking)
    5. Assign participants from the pool
    6. Inviting participants
    7. Session management
    8. Waiting list
    9. Invitations Scheduling and Digests
  6. Admission

    1. QR Code Admission
    2. Online Waiting Room
    3. Online Surveys and Completion Links
  7. Calendar

  8. Sending messages

    1. Invitation emails
    2. Confirmation emails
    3. Session reminder emails
    4. Default locale and date formats
    5. SMS and Push Notifications
  9. Settings

    1. Shibboleth Integration
    2. Kimai Integration
  10. Staff Management

  11. Finance

  12. Pools Management

Clone this wiki locally