Although most simple apps will operate just fine running on http://localhost:3000
(the default for Rails), it is often advantageous to run your local development on https, or using SSL/TLS. You may need to do this if you are developing an app that needs to be developed on https locally, for example, using advanced browser features like accessing the computer’s camera or other operations which Chrome forces you do on an https connection.
This guide will walk you through the steps to make that happen, and offer some useful added tips to make the process smooth.
1/ Decide Where Your Local Domain Will Be
This will be the made-up domain you will use for local development. Although it can be anything, I recommend it always ends with .localhost
or .local
. That way it’s easy to tell when you are doing development on your local machine.
Let’s assume the app I’m building is called “Abc”. For this and the rest of the examples, I will use abc.localhost
as my preferred localhost domain. That means we’ll set up for development at abc.localhost
, generate a self-signed key for abc.localhost
, etc.
2/ Add your Domain to your /etc/hosts
File
You will use your preferred shell editor (Vi or Emacs) to edit the /etc/hosts
file.
127.0.0.1 abc.localhost
After editing /etc/hosts, you may already have the DNS request for that domain cached. You can clear the cached entry by running dscacheutil -flushcache
on the command line.
Important: When editing your /etc/hosts
file, never change the top of the file where it says:
127.0.0.1 localhost
You also should add your entries to the end of the file, not to the beginning of it, and do not put entries above this line for localhost (it is used by the system).
3/ Create The SSL Config
In your existing Rails app, make a folder at config/ssl/
(The config/ folder exists by default, the ssl/ folder does not.)
In this folder, add a file called openssl.config
You will need this file only one time, in the next step, but you can check it into your repository as an artifact (a saved thing) in your app. Be sure to replace “abc.localhost” with your domain in the 3 spots in your file where is used and all of the rest of the examples.
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = abc.localhost
[v3_req]
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = abc.localhost
DNS.2 = *.abc.localhost
4/ Generate the KEY and CRT Files
openssl req -x509 -sha256 -nodes -newkey rsa:2048 -days 365 -keyout config/ssl/abc.localhost.key -out config/ssl/abc.localhost.crt -config config/ssl/openssl.config
Here, we are using OpenSSL to generate a new key and CRT file. This key is generated with an RSA 2048 signature (this refers to the strength of the encryption used) and is valid for 365 days from the day you generate it (so you’ll need to regenerate it once a year). You are telling OpenSSL to write your new key directly into a file at config/ssl/abc.localhost.key
and the CRT file into a file at config/ssl/abc.localhost.crt
. You are telling OpenSSL to use the config file (already created in step 3) at config/ssl/openssl.config
(Note that the -out and -keyout flags tell OpenSSL where to write to the file whereas -config specifies which file OpenSSL should read from. In this example, they just happen to be the same folder but this is just because of the way I set it up.)
Confirm that you now have your new keys at config/ssl/abc.localhost.key
and config/ssl/abc.localhost.crt
5/ Trust the Self-Signed Certificate In Your Keychain
Whereas Steps 1-4 can be done by just you or one developer, this step must be done but all developers on your team. For this reason, be sure to put this specific instruction into the README file of your app:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain config/ssl/abc.localhost.crt
Here you are adding the self-signed certificate you just made to your macOS keychain.
6/ Enable Invalid Certs in Chrome
In Chrome, browse to: chrome://flags/
Search for “insecure” and you should see the option to “Allow invalid certificates for resources loaded from localhost.” Enable that option and then scroll down to the “Restart” button at the bottom of this window.
7/ Force Your Rails App To Use SSL in Development
Add to your config/development.rb
file:
config.force_ssl = true
This will force all connections to be SSL. If they come in as non-SSL connections, Rails will automatically redirect to the HTTPS website. Important: Chrome will cache the redirect, meaning if you test it before making this change or unmake this change, Chrome will cache the fact that it saw the redirect previously and will re-use that redirect even when it is no longer present.
The way around this is to test in an Incognito Window.
8/ Tell Rails to Accept the Special Host
Add to your config/environments/development.rb
file:
config.hosts << "abc.localhost"
9/ Start Your Rails Server With the Self-Signed Cert
If you normally start your Rails server using rails server
, now you must do this:
bin/rails s -b 'ssl://0.0.0.0:3000?key=config/ssl/abc.localhost.key&cert=config/ssl/abc.localhost.crt'
If you are using ./bin/dev
(using JSBundling or Shakapacker) to start your server, that means you are using Foreman, a tool that starts several services at once.
In this case, go into your Procfile.dev file, and change the web
line to this:
web: bin/rails s -b 'ssl://0.0.0.0:3000?key=config/ssl/abc.localhost.key&cert=config/ssl/abc.localhost.crt'
Now, start your Rails server as you normally would with ./bin/dev
(If you are not doing peer-to-peer connections, you can instead use the syntax ssl://abc.localhost:3000
in Procfile above)
If you use abc.localhost
instead of 0.0.0.0, you will not be able to access it from another machine on your local network, which is necessary for peer-to-peer connections. See below for more discussion on peer-to-peer considerations.
10/ Go to https://abc.localhost:3000
in Your Browser
You should now see your website at https://abc.localhost:3000
in your browser with no errors. Confirm that the site is loading on an SSL connection by looking for the browser’s lock symbol.
TROUBLESHOOTING:
if you get:
Puma compiled without SSL support (RuntimeError)
That means that you are on a version of Mac OS that does not ship with OpenSSL support. To fix:
brew install openssl
gem uninstall puma
If it asks you which version of Puma to uninstall, choose All Versions.
bundle install
Also, you may need to try this from this SO post:
ruby -rpuma -e "puts Puma.ssl?"
gem install puma
ruby -rpuma -e "puts Puma.ssl?"
Addendum: Peer-to-Peer Considerations
If you are going to do peer-to-peer development using your primary computer as a rails server and connecting to it from another machine on your local network, there are some additional considerations. You will need this for testing peer-to-peer connections. For these examples, I will call the computer running the rails server as “the host” machine and the other one as “the client.” In this example, my local router is configured at a network that looks like 192.168.1.x (the DHCP-enabled router uses 192.168.1.1 as its own address and can give up to 255 local IP addresses in this range.) My host machine‘s local IP address is 192.168.1.2 and my client machine is running at 192.168.1.3
1/ Confirm that the two peers can see each other
Some networks allow peer computers to “see” (or be able to communicate with). Some networks don’t. Whether yours does depends on your IT setup— your router. You should 1) Use ping from the client computer to ping the host machine. For example, ping 192.168.1.2
Confirm that your Firewall on the host machine is either not turned on or allows for port 3000 to receive incoming connections.
2/ Instead of .localhost
TLD, use .local
This is because .localhost
TLDs are particular in the operating system. You can and should use .local
for both the host and the client machine. (If you already configured .localhost
for just the host machine, you will need to re-do all of the steps above.)
In Step #2 (above), on the host machine, you will add to /etc/hosts
entry pointing to 127.0.0.1
(“the loopback address”)
127.0.0.1 abc.local
On the client machine, you will add the local IP address of the host machine. Remember, in our example the host machine operates at a local address of 192.168.1.2.
So on the client machine (the other one), enter this in the /etc/hosts
192.168.1.2 abc.local
In Steps #3 & #4 above, you will create the self-signed certificate for a domain that ends with .local
(for example, abc.local
).
Steps #5 & #6 (above) must be done on both machines. Remember that to trust the certificate abc.local
you’ll need to copy the file abc.local.crt
to the client machine itself, either manually or by cloning your repo onto the client machine. Then run the add-trusted-cert command on the client machine.
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain config/ssl/abc.local.crt
In steps #8, #9, and #10 above you will use the .local
version of your self-signed cert domain.
3/ Start Your Rails Server at 0.0.0.0
Booting Rails+Puma has a prickly two-pronged mechanism that decides which requests will receive a response: 1) The hosts setting in Rails (configured in development.rb
) which is explained here in the Rails guides, and 2) the Puma address to bind on, let’s call it the “bound address.” (Puma is the default development server.)
The bound address is important because if the request is stopped there by Puma, it won’t even be logged into the Rails logs.
Normally, in development, this defaults to localhost
. That means that only localhost
and 127.0.0.1
will be responded to. When using self-signed certificates, you must tell Rails to accept the special domain name (Step #8 above).
You will also need to start Rails at 0.0.0.0, which is given in the default instructions in Step #9 above. (If you tell Puma to use the bound address of abc.local
, the peer machine won’t be able to access it because the request comes into 192.168.1.2 and Puma won’t respond.)
On the host machine, the operating system will route abc.localhost
to 127.0.0.1. Both versions of the domain will be accepted by Rails.
On the client machine, the request gets translated to the local IP address of the host machine (192.168.1.2 in the example above), and the Puma will allow only requests that match the bound address. Since the IP system doesn’t allow this, the only way to bypass the Puma-bound address mechanism is to use 0.0.0.0
(not the local domain which you have configured your self-signed cert for). That’s because 0.0.0.0
specifically allows requests to come in on any domain.
The default instructions in Step #9 above instruct you to boot your Rails server at 0.0.0.0
anyway.