Ruby on Rails (RoR) is a robust, open-source web development framework designed to help developers build applications efficiently. Written in the Ruby programming language, this framework provides a well-defined structure that organizes an application’s code, databases, and logic for seamless development. It follows the Model-View-Controller (MVC) architectural pattern, which separates the business logic, user interface, and controller—the component that acts as an intermediary between the user interface and business logic.
Despite its structured architecture, built-in security features, and rapid development capabilities, securing Ruby on Rails applications is crucial to protect them from potential vulnerabilities and cyber threats. Applications may become vulnerable to exploitation, cyberattacks, and data breaches without proper security measures.
In this Tutorial, we’ll walk you through setting up a secure Ruby on Rails environment, while digging into the best security tricks for today’s web apps. From configuration and authentication to encryption and monitoring, you’ll pick up key skills to shield your Rails app from threats.
Understanding Ruby on Rails security
Security vulnerabilities remain a major concern across all web applications. Many applications are built without proper security assessments and best practices, making them easy targets for exploitation. Even those built with Ruby on Rails aren’t immune to attacks if safeguards aren’t in place.
Thankfully, Rails provides built-in security features like CSRF protection, strong parameters, and automatic input sanitization to help defend against common threats. Let’s explore a few of the most critical threats and how Rails helps address them.
1. SQL Injection
SQL injection occurs when an attacker injects malicious SQL statements into a query, manipulating the database into executing unintended commands. For example, an improperly sanitized input field could allow an attacker to bypass authentication and gain access to sensitive user data.
Prevention in Rails:
- Use ActiveRecord’s parameterized queries, which automatically sanitize inputs:
User.where("email = ?", params[:email])
- Avoid string interpolation in queries, as it can lead to vulnerabilities.
- Implement least privilege access for database users to limit damage in case of a breach.
2. Cross-Site Scripting (XSS)
Cross-site scripting (XSS) is an attack where malicious scripts are injected into a webpage. When unsuspecting users visit the page, the injected script runs in their browser, making it possible for their credentials, session tokens, or other sensitive information to be stolen. Sometimes, attackers modify the page content to mislead users into taking unintended actions.
Prevention in Rails:
- Rails escapes user input by default, but always verify that your views do not render raw, unsanitized content.
- Use Rails helper methods like sanitize and h (HTML escape) to prevent script injection:
<%= sanitize(user_input) %>
- Set secure Content Security Policy (CSP) headers to restrict which scripts can execute in your application.
3. Cross-Site Request Forgery (CSRF)
CSRF attacks trick users into performing unintended actions while authenticated in a web application. An attacker can embed a hidden malicious request into a seemingly harmless webpage, tricking the victim into unknowingly submitting a form, changing account settings, or even transferring funds. Since the user’s session authenticates the request, the server assumes it’s legitimate and processes it.
Prevention in Rails:
- Rails includes CSRF protection by default. Ensure you have the
protect_from_forgery
directive enabled in your controllers:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
- Use authenticity tokens in all form submissions to validate requests.
- Implement SameSite cookie attributes to prevent cookies from being sent in cross-site requests.
Now that you’ve been exposed to the threats and security measures in Rails let’s look at how to set up a secure Ruby on Rails environment on an UpCloud server.
Preparing the environment
This section walks through setting up a secure Ruby on Rails environment on an UpCloud server running Ubuntu 24.04 LTS.
Prerequisites
Before we begin, ensure you have:
- UpCloud server running Ubuntu 24.04 LTS
- SSH access with root privileges to the UpCloud server
- UpCloud PostgreSQL Database running PostgreSQL 17.4
Installation and Setup
Step 1: Connect to Your Server
First, SSH into your UpCloud server as root:
ssh root@your-upcloud-ip
Step 2: Update and Secure the System
Update packages and reboot to apply changes:
apt update && apt upgrade -y
reboot # Reconnect after reboot
Step 3: Create a Secure Non-Root User.
Using a non-root user enhances security. Create a user ruby
, grant it sudo access, and switch to it. Why? Running applications as root is a security risk. This ensures Rails runs with limited privileges.
sudo useradd -m -s /bin/bash -G sudo ruby && echo "ruby:your-preferred-password" | sudo chpasswd
su - ruby
Step 4: Install Ruby 3.4.2 with rbenv
Installing the latest stable version of Ruby, like 3.4.2, is a best practice for security and performance. Newer versions include critical security patches, improved efficiency, and compatibility with the latest Rails version (8.0.2).
4.1. Install dependencies:
sudo apt install -y git curl libssl-dev libreadline-dev zlib1g-dev autoconf bison build-essential libyaml-dev libncurses5-dev libffi-dev libgdbm-dev
4.2. Set up rbenv
to manage Ruby versions:
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
4.3. Install ruby-build
plugin and Ruby 3.4.2:
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 3.4.2
rbenv global 3.4.2
Step 5: Install Node.js and Yarn
Rails requires JavaScript runtime support, so install Node.js 20.x and Yarn using the following command:
curl -sL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt install -y nodejs
sudo npm install -g yarn
Step 6: Configure Your UpCloud PostgreSQL Database
UpCloud offers two secure connection options for accessing your managed PostgreSQL database:
- Private connection via the Utility network (recommended)
- Public internet access with IP allowlisting and SSL
Choose the approach that fits your deployment environment. We’ll cover both.
6.1 Option 1: Secure with UpCloud’s Utility Network (Private Connection)
From your UpCloud DBaaS Overview page:
- Enable: Automatic access from Utility network servers.
- Disable: Allow access from all IPs.

This setup allows only servers inside your UpCloud account and within the same zone to communicate with the database over a private internal network. Traffic stays inside UpCloud’s internal infrastructure—safer and faster than routing over the public internet.
6.2 Option 2: Secure Public Access (if not using Utility Network)
If you’re not using private networking, and instead want to connect over the public internet:
- Enable: Public connection in your UpCloud DB instance settings

- Disable: Automatic access from Utility network server
- Disable: Allow access from all IPs
- Add: Your Rails server’s public IP under Allowed IPs

This method requires extra care. Always enforce SSL and limit access to trusted IPs only.
6.3: Take Note of Your DB Credentials
Still in the Overview section:
Go to the Private connection section (or Public connection, based on your setup)
- Note the host and port values
- Avoid using read-only or replica hosts — only use the primary connection

Scroll to the Auth section
- Take note of your username and password

Tip: You can also create your own database user instead of using the default.Go to the Users tab → click Create User → enter your desired username and password.

This lets you set up custom access control for different environments (e.g., development, staging, production).
6.4 Create Separate Databases for Development and Test
In the Databases tab:
- Click Create Database
- Create two databases named:
- secure_blog_development
- secure_blog_test
This allows Rails to manage separate environments securely and cleanly.

6.5 Install PostgreSQL Development Libraries
Install the necessary system packages for Rails to connect with PostgreSQL:
sudo apt install -y libpq-dev
These libraries are required for the pg gem to compile and work correctly.
Step 7: Install Rails and Create a New Application
7.1 Run the command below to install the latest stable version of Rails:
gem install rails
7.2. Create a new Rails application with PostgreSQL as the database:
rails new secure_blog --database=postgresql
cd secure_blog
Step 8: Configure Rails Database Credentials
8.1. Edit the database.yml
file to match PostgreSQL credentials:
nano config/database.yml
8.2. replace development and test sections with:
development:
adapter: postgresql
database: secure_blog_development
username: <%= ENV['DB_USERNAME'] %>
password: <%= ENV['DB_PASSWORD'] %>
host: <%= ENV['DB_HOST'] %>
port: <%= ENV['DB_PORT'] %>
test:
adapter: postgresql
database: secure_blog_test
username: <%= ENV['DB_USERNAME'] %>
password: <%= ENV['DB_PASSWORD'] %>
host: <%= ENV['DB_HOST'] %>
port: <%= ENV['DB_PORT'] %>
Click on ctrl x, y, and enter to save.
Note: Hardcoding credentials in config files can be a security risk. Instead, use environment variables:
8.3. Set these in the terminal:
export DB_USERNAME=your_upcloud_DB_username
export DB_PASSWORD=your_upcloud_DB_password
export DB_HOST=your_upcloud_DB_hostname # Use private or public hostname
export DB_PORT=your_upcloud_DB_port
Step 9: Initialize Database and Start Rails Server
Run the following commands to set up the database and start the Rails app:
rails db:migrate
rails server --binding=0.0.0.0
Your Rails app should now be accessible at: http://your-server-ip:3000
With the application successfully running, let’s take a look at some best practices for securing your Rails application, covering authentication, encryption, and vulnerability protection.
Securing the rails configuration
Securing a Ruby on Rails application starts at the configuration level. Misconfigured settings can expose sensitive data, create vulnerabilities, or allow attackers to manipulate the system. Let’s explore key measures to fortify your Rails application’s configuration.
1. Environment Configuration Files
As seen in Step 8.2, we avoided hardcoding database credentials in database.yml. While hardcoding these values may not pose an immediate risk in a local development environment, it is a critical vulnerability in production. Exposing this file in a .git repository automatically leaks sensitive data, increasing the risk of unauthorized access.
Rails’ Encrypted Credentials: A Secure Alternative
To enhance security, Rails replaced secrets.yml
with config/credentials.yml.enc
, encrypting secrets by default. Instead of manually setting credentials via export commands, store them in credentials.yml.enc
for added security.
Run the following command to edit encrypted credentials:
EDITOR=nano rails credentials:edit
Add your sensitive data inside:
secret_key_base: your_random_key_here
db_password: securepassword
Then, reference it securely in database.yml:
production:
adapter: postgresql
database: secure_blog_production
username: blog_user
password: <%= Rails.application.credentials.db_password %>
The config/credentials.yml.enc
file, even when committed to a Git repository, remains secure because it is encrypted. It can only be decrypted using the config/master.key
file, which is never committed to Git and should always be stored securely.
2. Strong Parameters
Unrestricted user input is one of the most overlooked security risks in web applications. Attackers can exploit input vulnerabilities to modify data, escalate privileges, or inject malicious code. Rails enforce Strong Parameters to mitigate this risk, preventing mass assignment vulnerabilities.
Understanding Strong Parameters
In 2012, GitHub suffered a breach when an attacker injected unauthorized parameters, allowing them to modify user account settings. This happened because version 3 of Rails, which was used then, did not restrict which parameters could be updated; this allowed attackers to manipulate data at will.
Rails 4 to 8.0.2 prevents this by enforcing strong parameters:
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: "Post created!"
else
render :new
end
end
private
def post_params
params.require(:post).permit(:title, :content)
end
end
require(:post):
Ensures a post key exists in params.permit(:title, :content):
Explicitly allows only title and content.
Without strong parameters, an attacker could send:
{
"post": {
"title": "Hacked Post",
"is_admin": true
}
}
If Rails didn’t enforce parameter whitelisting, this could grant admin privileges, manipulate account settings, or alter sensitive fields.
Rails 8.0.2 strictly enforces mass-assignment security, ensuring that unpermitted attributes raise an error instead of silently overriding database records.
3. Whitelisting Parameters
A common mistake in web security is relying on blacklisting (e.g., blocking specific fields like admin). However, blacklists fail when new fields are introduced, allowing attackers to bypass restrictions.
Instead, Rails enforces a whitelisting approach, processing only explicitly permitted fields.
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
If an attacker attempts:
{
"user": {
"name": "Attacker",
"admin": true
}
}
Rails will automatically discard the admin field, preventing unauthorized privilege escalation.
Handling Nested Parameters Safely
For complex forms, such as posts with tags, Rails allows structured whitelisting to prevent arbitrary nested input.
def post_params
params.require(:post).permit(:title, :content, tags: [:name])
end
This ensures that only title, content, and tags[:name] are accepted—anything else is ignored.
Authentication and Authorization best practices.
Authentication verifies who users are, while authorization dictates what they can do. Getting these right is non-negotiable for a secure Rails app. Proper systems and tools must be in place to ensure applications adhere to industry security standards. Techniques like Role-Based Access Control (RBAC) and Multi-Factor Authentication (MFA) are crucial in securing modern applications. Let’s explore some.
1. Secure User Authentication
Authentication is used to confirm user identities. Ruby provides reusable authentication packages (Ruby gems) to streamline this process. Popular options include:
- Devise – A full-featured authentication system.
- Pundit – A policy-based authorization library.
- Sorcery – A lightweight authentication alternative.
Using Devise for Authentication
Devise is one of the most popular authentication solutions for Rails. It provides an out-of-the-box system for:
- User sign-up, login, and logout
- Password recovery and account confirmation
- Session management and authentication security
To integrate Devise into your Rails application:
cd ~/secure_blog
nano Gemfile
Add Devise to the Gemfile:
gem 'devise'
Then install the gem:
bundle install
rails generate devise:install
This automates authentication, saving developers time while ensuring secure user handling.
Enhancing Password Security with bcrypt
Basic password hashing is not enough to prevent brute-force attacks. Bcrypt enhances security by:
- Adding a piece of data referred to as “salt” to the hash makes password hashes unique and resistant to rainbow table attacks.
- Slowing down brute-force attempts by using computationally expensive hashing.
Devise already depends on bcrypt, so no additional setup is required when using it. However, if manually implementing authentication, install bcrypt separately:
gem 'bcrypt', '~> 3.1.7'
2. Role-Based Access Control (RBAC)
While authentication ensures users can log in, authorization determines what they can access. Implementing RBAC prevents unauthorized actions by restricting user roles.
A least-privilege system ensures users only have access to what they need:
- User – Can view posts only.
- Editor – Can view and write posts but not delete them.
- Admin – Has full privileges (view, write, delete).
Policy Object Pattern for RBAC in Rails
Using Policy Objects, we can define rules, conditions, and logic to manage user permissions in Rails.
Example: Implementing a Policy Object in Rails:
# app/policies/post_policy.rb
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
@user = user
@post = post
end
# Can the user view the post?
def show?
true # All roles can view posts
end
# Can the user create a post?
def create?
user.editor? || user.admin? # Editors and admins can create posts
end
# Can the user edit a post?
def update?
(user.editor? && post.author == user) || user.admin? # Editors can edit their posts; admins can edit any post
end
# Can the user delete a post?
def destroy?
user.admin? # Only admins can delete posts
end
end
This decouples business logic from controllers, keeping authorization clean and scalable.
3. Two-Factor Authentication (2FA) for Enhanced Security
Credential leaks and phishing attacks can still compromise accounts, even with strong passwords. 2FA adds an extra security layer, requiring users to verify their identity with an additional authentication factor (e.g., a one-time password (OTP) or authenticator app).
To integrate 2FA with Devise, use the devise-two-factor gem:
gem 'devise-two-factor'
Then, configure it within your User model:
class User < ApplicationRecord
devise :two_factor_authenticatable
end
Note: You’ll need a database tweak—like an otp_secret column
and some setup for OTP generation.
Encryption and Data Security
Encryption is essential for securing applications and websites, whether data is in transit or at rest. HTTPS is non-negotiable in modern workloads as it encrypts communication between a user’s browser and the server, protecting sensitive data from interception.
Rails provides the force_ssl
configuration to ensure the applications always run under HTTPS.
To enforce encryption in production environments, enable the force_ssl
flag in config/environments/production.rb:
config.force_ssl = true
To enforce HTTPS across all environments, enable force_ssl
in config/application.rb
config.force_ssl = true
However, enforcing HTTPS at the application level is not enough—the server itself must be configured correctly. For deployments within UpCloud servers, this involves:
Installing Nginx to handle incoming traffic:
sudo apt install nginx
Setting up SSL certificates with Let’s Encrypt (Certbot):
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Configuring Nginx to redirect HTTP to HTTPS and enable SSL/TLS:
erver {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
}
Encryption in Transit
Data transmitted between a client (browser, API, or external service) and a server must be encrypted in transit to prevent man-in-the-middle (MITM) attacks.
HTTPS uses TLS (Transport Layer Security) to encrypt all communication, ensuring:
- Confidentiality – Prevents attackers from reading sensitive data.
- Integrity – Protects against data tampering during transmission.
- Authentication – Ensures the server is legitimate and not an imposter.
Encryption at Rest
For data stored in the database, application-level encryption is crucial. Rails provides Active Record Encryption, allowing developers to declare specific attributes to be encrypted. It means that;
- The application reads unencrypted data, but the database stores it in an encrypted format.
- Data is encrypted & decrypted automatically when accessed.
Example of encrypting fields in a Rails model:
class User < ApplicationRecord
encrypts :email, :phone_number
end
With this setup:
- The database stores email and phone number in an encrypted form.
- The Rails app decrypts them when accessed but prevents unauthorized access in case of a database breach.
Securing API Keys
Hardcoding API keys poses a serious security risk, especially if the codebase is exposed publicly. API keys must be stored securely to prevent unauthorized use.
One approach is using environment variables with the dotenv-rails
gem:
Install dotenv-rails
by adding it to the Gemfile:
gem 'dotenv-rails'
Create a .env
in the root directory and store your variables
API_KEY=up15281cloud
Access the key inside your Rails app:
ENV['API_KEY']
Always add .env
to .gitignore
to prevent secret leaks in version control:
Monitoring and Logging
After setting up an application, proper logging and monitoring are essential for ensuring its health and smooth operation. Developers rely on logs to pinpoint issues, troubleshoot errors, and track security events. Fortunately, Rails has built-in logging and monitoring capabilities that can be enhanced using gems like:
- Lograge – which formats logs in a more structured and readable manner.
- New Relic’s Ruby APM – which tracks response times, database queries, and crashes.
Rails allows custom event logging using ActiveSupport::Notifications:
Rails.logger.info "Suspicious login attempt from IP: #{request.remote_ip}"
Hosting Rails on an UpCloud cloud server provides real-time infrastructure monitoring, tracking:
- CPU usage – Detects excessive resource consumption.
- Memory & disk usage – Flags potential bottlenecks.
- Network traffic – Helps identify unusual spikes or potential attacks.
Using these tools security threats can be spotted, watch out for:
- Spikes in 404s – Could indicate probing for vulnerabilities.
- Repeated failed logins – A potential brute-force attack.
- Unusual traffic surges – UpCloud’s dashboard can flag suspicious activity.
Performance Optimization and Scalability
A slow application leads to poor user experience, high bounce rates, and lost revenue. Users are more likely to switch to an alternative if a competing app loads even a few seconds faster.
Building a Ruby on Rails application on UpCloud’s high-performance infrastructure is a great start, but proper optimization techniques ensure:
- Better user experience (faster load times).
- Lower latency (quicker responses to user actions).
- Scalability (ability to handle increasing traffic).
Two essential techniques for performance and scalability are caching and CDN integration.
Caching
Caching stores frequently accessed data in memory to reduce server load and improve response times. Rails have built-in caching mechanisms, which includes:
- Fragment Caching – Caches parts of a page (e.g., a frequently displayed sidebar, comments section, or navigation bar). It is used to avoid regenerating content that doesn’t change often.
Example:
<% cache @article do %>
<%= render @article %>
<% end %>
If this article was loaded before, Rails fetches it from cache instead of reloading it from the database.
- Database Query Caching – Rails remembers the result of a database query during a request cycle to avoid redundant lookups.
Example:
ActiveRecord::Base.cache do
Post.find(1) # Runs a database query
Post.find(1) # Retrieves result from cache, no new query
end
The second query fetches the result from memory instead of hitting the database, reducing load time.
How UpCloud Enhances Caching Performance
- High-performance SSD storage reduces database read/write times.
- Memory-optimized compute instances help with caching-heavy applications.
Content Delivery Network (CDN)
A Content Delivery Network (CDN) improves application performance by caching and serving content from servers closer to users. This reduces:
- Load on the origin server.
- Latency by serving static files faster.
- Bandwidth usage, optimizing performance at scale.
Conclusion
Securing a Ruby on Rails application goes beyond just writing clean code—it requires a proactive approach to security, performance, and scalability. You can avoid vulnerabilities and protect your workload from threats by adhering to best practices like configuring secure environment files, enforcing HTTPS, implementing strong authentication, encrypting sensitive data, and monitoring logs.
Plus, you can ensure your application remains fast and scalable by using performance optimizations like caching, CDNS, and UpCloud’s high-performance servers and infrastructures.
However, security is a continuous task. Regularly updating dependencies, applying security patches, and monitoring security advisories are crucial to staying immune to potent risk. Building a secure and scalable Rails application is about continuous improvement—stay informed, implement best practices, and keep your application resilient and future-ready.