Rails Gets Security Right by Default. Everyone Else Doesn't.
Rails Gets Security Right by Default. Everyone Else Doesn't.
This is not a "use Rails" post. This is a "look at what good framework defaults can do" post.
Rails has a single configuration line that has done more for web security than thousands of blog posts about HTTPS best practices:
config.force_ssl = true
That one line, typically enabled in config/environments/production.rb, does all of this:
- Redirects all HTTP requests to HTTPS
- Sets the
Strict-Transport-Securityheader (HSTS) with a one-year max-age - Marks all cookies as
Secure(only sent over HTTPS)
Three critical security checks. One line. Enabled by default in every new Rails app since Rails 5.
What that looks like in Next.js
Next.js doesn't have an equivalent. To get the same protection, you need to configure multiple layers across multiple files.
HTTPS redirect — handled by your hosting provider (Vercel does this automatically, but self-hosted Next.js doesn't):
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (
request.headers.get('x-forwarded-proto') !== 'https' &&
process.env.NODE_ENV === 'production'
) {
return NextResponse.redirect(
`https://${request.headers.get('host')}${request.nextUrl.pathname}`,
301
)
}
return NextResponse.next()
}
HSTS header — requires custom header config:
// next.config.js
const nextConfig = {
headers: async () => [{
source: '/(.*)',
headers: [{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload'
}]
}]
}
Secure cookies — must be set manually on every cookie:
cookies().set('session', token, {
secure: true,
httpOnly: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7,
})
That's middleware, config, and per-cookie settings across three different files. And this only covers transport security — it doesn't touch the other security headers that Rails middleware handles (CSRF tokens, X-Frame-Options, X-Content-Type-Options).
The benchmark data tells the story
In our Security Headers & Basics audit, the Transport Security category checks for exactly these things: HTTPS enforcement, HSTS, secure cookies, and SameSite attributes. Four checks. The difference between frameworks that default these on versus frameworks that leave them to the developer is stark.
Projects built on frameworks with strong security defaults consistently score 85-95% on transport security. Next.js projects — even well-maintained ones with dedicated security teams — average around 65-75% overall on the full security headers audit because they have to remember to configure each protection individually.
It's not that Next.js developers are less security-conscious. It's that Next.js asks them to remember more things.
What good defaults actually look like
Rails isn't the only framework that gets this right. Here's what "secure by default" means in practice:
Django sets SECURE_SSL_REDIRECT, SECURE_HSTS_SECONDS, SESSION_COOKIE_SECURE, and CSRF_COOKIE_SECURE — all configurable, but the framework's deployment checklist (manage.py check --deploy) will warn you if they're not enabled.
Phoenix (Elixir) generates production config with force_ssl in the endpoint and secure cookie settings out of the box.
Laravel includes CSRF middleware, encrypted cookies, and HTTPS helpers by default.
The pattern: mature server-side frameworks treat security configuration as part of the framework's responsibility, not the developer's homework.
The JavaScript ecosystem's gap
The JavaScript framework ecosystem has a philosophical difference: minimal defaults, maximum flexibility. Next.js, Nuxt, SvelteKit, and Remix all ship with essentially zero security headers configured. The reasoning is sound — these frameworks run in diverse environments (serverless, containers, edge, static), so prescriptive defaults might break things.
But the result is predictable. In our audit data across 30 open-source projects:
- Basic Hygiene (
.envfiles, no hardcoded secrets, lockfiles): 85%+ average. Developers handle this because it's part of their daily workflow. - Security Headers (CSP, HSTS, Permissions-Policy, X-Frame-Options): below 60% average. Developers miss this because no framework tells them to set it up.
The gap isn't knowledge. It's defaults.
What this means for AI-generated code
This matters even more in the age of vibe coding. When you ask an AI tool to build you a Next.js app, it generates working code. Routes work. Auth works. The database connects. But the AI doesn't add security headers for the same reason developers don't: the framework doesn't prompt for them, nothing breaks without them, and the happy path works fine.
Rails apps built by AI tools fare better on our security audits — not because the AI knows more about security, but because config.force_ssl = true is in the generated boilerplate. The AI doesn't need to know what HSTS is. The framework handles it.
The fix isn't "switch to Rails"
The fix is recognizing that framework defaults have a massive impact on baseline security. If you're building with Next.js, Nuxt, or SvelteKit:
- Run a security headers audit early. Don't wait until pre-launch. The Security Headers & Basics audit is free and takes 15 minutes.
- Create a security headers config once. Put it in your
next.config.jsor middleware and forget about it. Every project. Every time. - Use a template. If you're starting projects frequently, build a starter that includes security headers. Be your own framework defaults.
The difference between a B and a D on our security audit often comes down to five lines of header configuration that take 30 seconds to add — if you know they're needed.
Rails developers don't need to know. That's the point.