TL;DR: You can and should use localhost in development. Browsers give a special treatment to the http://localhost
domain. Even though it's on HTTP, most of the time it will be treated as HTTPS. So any browser APIs that need HTTPS, will continue working even if you're using localhost. When you do need HTTPS locally, use Caddy.
Also, localhost can have subdomains. So you can just prefix the URL with your app name, and suffix it with the port to separate multiple apps, like http://{subdomain}.localhost:{port}
. I had no idea about this until I saw this tweet from DHH.

and again in the Rails World keynote...

Secure Contexts
Browsers normally only treat HTTPS origins as secure contexts. But there’s a special exception for local development: certain origins are treated as potentially trustworthy even if they don’t use TLS.
That list includes:
http://127.0.0.1
http://localhost
- Any subdomain ending in
.localhost
(e.g.http://app.localhost
)
The reasoning is simple: when you access these addresses, the request never leaves your machine. There’s no network hop where data could be intercepted. So, although it’s technically plain HTTP, the browser considers it safe enough to behave as if it were secure.
Using subdomains with localhost
(like blog.localhost:3000
) has several practical benefits for local development, especially when working on complex Rails apps. They make your development environment behave more like production, helping you test cookies, sessions, routing, and multi-app setups, without extra DNS or host configuration.
Here're a few use-cases for localhost domains with a custom subdomain.
1. Simulating production-like domains
- In production, you might serve different parts of your app on different subdomains (e.g.,
blog.example.com
,api.example.com
). - Using
blog.localhost
mimics this locally, so you can test routing, cookies, CORS, and redirects in an environment closer to production.
2. Handling cookies & sessions correctly
- Browsers scope cookies to a domain.
- With subdomains (e.g.,
blog.localhost
,admin.localhost
), you can:- Test shared cookies across subdomains.
- Verify session handling when different subdomains need to share authentication (like SSO).
3. Testing multi-tenant or subdomain-based routing
- Rails apps often use subdomain-based routing for multi-tenancy (
tenant1.example.com
,tenant2.example.com
). - Using
tenant.localhost
allows you to test these routes without production domains.
4. Easier separation for multiple apps
- If you’re running multiple apps (e.g.,
writesoftwarewell.localhost:3000
,typeangle.localhost:3001
), subdomains help keep them organized and accessible.
5. No extra setup needed
localhost
is a special-use domain that browsers resolve without DNS.- Any
*.localhost
automatically resolves to127.0.0.1
. No/etc/hosts
changes are required.
What if I do need local HTTPS?
There're some edge cases when you may want your local development app to behave very similar to the one in production, which may include using HTTPS during development. This is the case when you want to debug an issue that occurs on HTTPS but not on HTTP, or you want to locally test third-party APIs that need HTTPS.
You can run the Rails app locally on HTTPS with the Caddy server. Caddy is an awesome web server to serve your sites, services, and applications.
First, install Caddy.
$ brew install caddy
We have the Rails app running on the port 3000. We'll use Caddy to as a reverse proxy server that will accept requests over HTTPS and forward them to the Rails app. In Caddy, setting up a reverse proxy is as simple as this:
blog.localhost {
reverse_proxy :3000
}
Add the following code in a new file called Caddyfile
, stored in your Rails app.
Now, you can run Caddy from your Rails app.
$ caddy run
The run
command starts Caddy in the foreground, i.e. you see the output. If you want to run it in the background, run the caddy start
command. However, I suggest running it in the foreground.
In a separate terminal window / tab, launch the Rails server as usual.
$ rails server
Now visit the url endpoint https://blog.localhost
. The very first time, it will take a second to load as Caddy needs to generate a self-signed certificate for the blog.localhost
domain. But you should quickly see your app running on HTTPS.

We're almost done, but there is one small quality-of-life enhancement we can make. Right now, it's quite cumbersome having to run a separate commands to launch the Caddy server. Let's move the caddy run
command in the Procfile
to launch Caddy along with the Rails server and TailwindCSS compiler.
Here's the updated Procfile.dev
:
web: bin/rails server
css: bin/rails tailwindcss:watch
caddy: caddy run
That's it. Now you can run bin/dev
to launch your application along with Caddy.
Visit https://blog.localhost
to ensure the app is up and running and serving requests.
That's a wrap. I hope you found this article helpful and you learned something new. In short: localhost traffic isn’t encrypted, but because it’s confined to your own box, browsers treat it as “trustworthy enough” to enable features that normally require HTTPS.
As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. Your email is respected, never shared, rented, sold or spammed. If you're already a subscriber, thank you.