rails
Configuring Rails and Vite to use HTTPS in local development
The localhost gem does most of the heavy-lifting.
By default, when Rails starts a server in development, it listens on localhost:3000
using regular HTTP. To more closely simulate a production scenario, I wanted to configure my Rails app to use HTTPS instead. Here’s how that works.
Install the localhost gem
Rails uses the puma web server, which is capable of serving HTTPS as long as it is provided an appropriate private key and certificate. This is where the localhost gem comes in.
The localhost gem integrates directly with puma to generate a key and certificate; it then makes those available to puma during the startup process.
group :development do
gem "localhost"
end
Add the localhost gem and run
bundle install
.
Start the Rails server with ssl specified
Even with the localhost gem installed, Rails will still default to HTTP on port 3000 unless told otherwise. To instruct Rails to use HTTPS, I start Rails like this:
bin/rails s -b ssl://localhost:4000
Trust the certificate
The first time visiting https://localhost:4000
, the browser will complain about an untrusted certificate. This is because the certificate generated by the localhost gem is self-signed.

Browser setup
The specifics are different for each browser, but generally speaking, I needed to dismiss the security warning and add the certificate to the operating system’s registry to avoid future warnings. It takes just a few clicks, following these guides:
OpenSSL
In addition, in case I need to use Ruby to make HTTPS connections to localhost, it is a good idea to make OpenSSL was aware of the certificate as well. An explanation can be found on Stack Overflow, but to make a long story short:
$ cp ~/.localhost/localhost.crt /usr/local/etc/openssl@3/certs/
$ /usr/local/opt/openssl@3/bin/c_rehash
/opt/homebrew
instead of /usr/local
.
Configure the vite dev server to use HTTPS
With Rails now using HTTPS, I needed to configure Vite to use HTTPS as well, if I wanted hot-reloading to work seamlessly in the browser. Whereas the localhost gem magically passes its key and certificate to puma, with Vite I needed to be more explicit:
import { defineConfig } from "vite";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { homedir } from "node:os";
const certPath = resolve(homedir(), ".localhost/localhost.crt");
const keyPath = resolve(homedir(), ".localhost/localhost.key");
const https = existsSync(certPath)
? { key: readFileSync(keyPath), cert: readFileSync(certPath) }
: {};
export default defineConfig({
plugins: [
// ...
],
server: { https },
});
Specify
server.https
using key and certificate generated by the localhost gem.
The vite_ruby config needed to be updated as well:
"development": {
"autoBuild": true,
"https": true,
"publicOutputDir": "vite-dev",
"port": 3036
}
Add
"https": true
to the development settings.
Disable HSTS if using force_ssl in development
With everything working, I was tempted to set config.force_ssl=true
in my Rails development config to further simulate a production environment. However, this caused problems because it enables HSTS by default. Once my browser saw the HSTS header, it now required all apps hosted on my machine to use HTTPS. That’s a bit more than I bargained for!
To prevent security restrictions from one app spilling over onto other apps, I recommend turning off HSTS (in development only):
config.force_ssl = true
config.ssl_options = {hsts: false}
If using
force_ssl=true
in development, consider turning off HSTS.