· Jesse Edwards · Case Studies  · 4 min read

The Single Server Fortress: Pragmatic Defense in Depth

How to build professional grade security layers on a lean, single node stack using Rails and Kamal.

How to build professional grade security layers on a lean, single node stack using Rails and Kamal.

The Single Server Fortress: Pragmatic Defense in Depth

Building professional grade security on a lean, single node stack.


The Philosophy: Layers, Not Just Walls

In my time at JP Morgan Chase, we had the luxury of sprawling VPCs and dedicated security teams. In the startup world, I bring all the experience I have learned over the years to lock down the fort while staying lean.

Defense in Depth (DiD) is about ensuring that even if one layer has a gap, the next layer catches the threat. For RenovationRoute, we’ve built a “concentric circle” strategy that treats our single server like a fortress.


Layer 1: Network Locality (The “Ghost” Database)

The Reality: We run our App, Database, and Redis on a single production server.

The Defense:

  • Zero Public Exposure: Our Postgres and Redis ports are not open to the internet. By not exposing these ports in our firewall and binding them internally, the database is “invisible” to anyone outside the server.
  • Docker Internal Networking: The Rails app communicates with the database over a private Docker bridge network using internal hostnames (rr-db).

The Result: Unless an attacker has a shell on the actual production hardware, they cannot even attempt to handshake with the database.


Layer 2: Host Hardening (The Foundation)

The Threat: Brute force SSH attacks or “Zero-Day” exploits in OS level packages.

Our Solution: Key-Only SSH, Whitelisted IPs through VPN, Fail2Ban

  • No Passwords: We have disabled SSH password authentication entirely. No key, no entry.
  • Whitelisted IP: Only one IP can communicate with the server.
  • Fail2Ban: This is our OS level “bouncer.” If an IP starts sniffing for open ports or attempting failed handshakes, Fail2Ban drops their traffic at the firewall level before they even touch our application memory.

Layer 3: The Rails Gateway (Identity & Authentication)

The Threat: API scraping or unauthenticated users poking around sensitive endpoints.

Our Solution: Global Authentication Lockdown

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # We treat every request as hostile until proven otherwise.
  before_action :authenticate_user!
end

The Impact: We treat Identity as the first barrier. By default, unauthenticated users see nothing, effectively hiding our data structure from the public.


Layer 4: Application Logic (The IDOR Killer)

The Threat: IDOR (Insecure Direct Object Reference). Example is when user A, who does not own a payment tries to access user B’s payment by guessing a URL ID.

Our Solution: UUIDs + Pundit Policies

UUIDs: We use random UUIDs instead of sequential integers (/payouts/1). This makes “walking the database” mathematically impossible.

Pundit: We use explicit Policy objects to verify every action.

# app/controllers/payouts_controller.rb
def approve
  @payout = Payout.find(params[:id])

  # Just because you're logged in doesn't mean you have access.
  # We check: "Does this specific user have the appropriate role for this project?"
  authorize @payout

  @payout.release_funds!
end

The Full Stack Defense Table

LayerComponentDefense MechanismPurpose
NetworkDocker BridgeNo Public Port MappingIsolate DB from the Internet
HostLinux/EC2SSH Keys OnlyPrevent Server Takeover
App FrameworkRailsStrong Params & UUIDsPrevent SQLi & Discovery
Business LogicPunditPolicy-Based AuthPrevent IDOR / Multi-tenant leaks
IdentityDeviseLockable AccountsPrevent Credential Stuffing

What We’re NOT Doing (And Why)

Complex VPC Multi Node Networking

Reason: When your database and app live on the same box, a VPC adds latency and cost without a significant security gain for a small scale SaaS. We prefer the Network Locality of a single node setup until we need to scale horizontally. When We scale, will discuss the appropriate VPC and multi node architecture.

MFA (In Testing)

Reason: We are testing hardware key support. In security, a poorly implemented MFA is worse than no MFA, so we are taking the time to get the recovery flows right and test all of the edge cases before rolling it out to production. Last thing we want is to lock out legitimate users or create a bypass.


Conclusion: The Architect’s Mindset

Defense in Depth isn’t about having a 50 person security team. It’s about knowing where your risks are and layering defenses to cover them.

By keeping our database ports off the public internet and using explicit authorization policies, we’ve built a platform that is secure by design.

All systems are different, and the right security measures depend on the specific risks and architecture of your application. In my case part of my design goals for the architecture was to make it as secure as possible. I am dealing with real transactions.

Back to Blog

Related Posts

View All Posts »
Why I Built RenovationRoute

Why I Built RenovationRoute

Most construction software was not built by people who lived through the problems. RenovationRoute exists because the failures I kept seeing were not technology problems. They were architecture, process, and trust problems.