Repository navigation
Webserver and SSL Setup
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.
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:
- Terminate SSL/TLS encryption (HTTPS on port 443).
- Redirect unencrypted HTTP traffic (port 80) to HTTPS.
- Serve static assets efficiently.
- Manage client connections, timeouts, and file upload limits.
[ Web Browsers ] ─── HTTPS (Port 443) ───► [ NGINX / Apache ] ─── HTTP (Port 3000 / Socket) ───► [ Puma / Rails ]
NGINX paired with Rails' built-in Puma application server is the modern standard for deploying Rails applications.
On Ubuntu / Debian:
sudo apt update
sudo apt install -y nginxCreate a systemd unit file to manage the Puma background process automatically:
sudo nano /etc/systemd/system/hroot.servicePaste 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.targetEnable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable --now hroot
sudo systemctl status hrootCreate a dedicated NGINX configuration file for your hroot domain:
sudo nano /etc/nginx/sites-available/hroot.confInsert 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 nginxIf your university or organization requires Apache, use Phusion Passenger with mod_ssl.
Follow the official Passenger APT repository instructions:
sudo apt install -y apache2 libapache2-mod-passenger
sudo a2enmod ssl headers rewriteCreate /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 apache2Let's Encrypt provides free, trusted, automatically renewed SSL certificates.
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-apacheRun 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.
Certbot automatically installs a systemd timer that checks certificate validity twice daily.
To verify the timer is active:
systemctl status certbot.timerTo test that renewal works seamlessly without waiting for expiration:
sudo certbot renew --dry-runTip
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.shMany universities and research institutes issue official organization-validated certificates through institutional PKIs.
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.pemMove 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- 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
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=trueWhen FORCE_SSL=true:
- Cookies are sent exclusively over encrypted connections (
Secureflag). - 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/hrootcurl -I https://hroot.example.orgExpected response headers:
HTTP/2 200
strict-transport-security: max-age=63072000; includeSubDomains; preload
x-frame-options: SAMEORIGIN
x-content-type-options: nosniffVerify that Puma is listening only on 127.0.0.1:3000 (not accessible directly from the public internet):
ss -tuln | grep 3000Output should be: 127.0.0.1:3000.
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
-
-
Subject pool and user management
-
Experiment management