Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
localhost SSL with puma
# 1) Create your private key (any password will do, we remove it below)
$ cd ~/.ssh
$ openssl genrsa -des3 -out server.orig.key 2048
# 2) Remove the password
$ openssl rsa -in server.orig.key -out server.key
# 3) Generate the csr (Certificate signing request) (Details are important!)
$ openssl req -new -key server.key -out server.csr
# IMPORTANT
# MUST have localhost.ssl as the common name to keep browsers happy
# (has to do with non internal domain names ... which sadly can be
# avoided with a domain name with a "." in the middle of it somewhere)
Country Name (2 letter code) [AU]:
...
Common Name: localhost.ssl
...
# 4) Generate self signed ssl certificate
$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
# 5) Finally Add localhost.ssl to your hosts file
$ echo "127.0.0.1 localhost.ssl" | sudo tee -a /private/etc/hosts
# 6) Boot puma
$ puma -b 'ssl://127.0.0.1:3000?key=/Users/tadas/.ssh/server.key&cert=/Users/tadas/.ssh/server.crt'
# 7) Add server.crt as trusted !!SYSTEM!! (not login) cert in the mac osx keychain
# Open keychain tool, drag .crt file to system, and trust everything.
# Notes:
# 1) Https traffic and http traffic can't be served from the same process. If you want
# both you need to start two instances on different ports.
#
#
@Abbabon

This comment has been minimized.

Copy link

commented Jul 28, 2015

Thanks for the detailed explanation! Works like a charm.

However, google chrome prevents you from accessing the website without going through their warnings ("Your connection is not private") - not a critical issue, but maybe I did something wrong.

@THPubs

This comment has been minimized.

Copy link

commented Aug 16, 2015

Yes I also face the same issue. Any fix?

@tobiasfeistmantl

This comment has been minimized.

Copy link

commented Sep 21, 2015

Hi Guys,

you both get the error because of missing trust into the certificate. To fix this problem you need to add it to the trusted certificates. Look at point 7. :)

@ma11hew28

This comment has been minimized.

Copy link

commented Oct 27, 2015

Can I just use localhost so that I can keep my hosts file clean? If not, why not? This post seems to suggest that you can: http://stackoverflow.com/q/18617709/242933

@u007

This comment has been minimized.

Copy link

commented Apr 18, 2016

👍

@fagiani

This comment has been minimized.

Copy link

commented Sep 1, 2016

@mattdipasquale 👍

@A9u

This comment has been minimized.

Copy link

commented Sep 9, 2016

Point 7 for ubuntu:-
sudo cp server.cert /usr/local/share/ca-certificates/
sudo update-ca-certificates

It worked for me. For more details, check here

@dinesh16

This comment has been minimized.

Copy link

commented Dec 2, 2016

Great Thanks!

@lvela

This comment has been minimized.

Copy link

commented Dec 15, 2016

Running without rails server shows only the puma logs. To get the standard development logs, add to config/environments/development.rb

config.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
@timoschilling

This comment has been minimized.

Copy link

commented Jan 27, 2017

You can serve https and http with on process by starting it with:
puma -b 'ssl://127.0.0.1:3000?key=/Users/tadas/.ssh/server.key&cert=/Users/tadas/.ssh/server.crt' -b 'tcp://127.0.0.1:3001'
https runs on port 3000 and http on 3001

@gregawoods

This comment has been minimized.

Copy link

commented Feb 9, 2017

This stopped working for me after upgrading Puma to 3.7.0. Locking in to version 3.6.2 resolved my issue.

2017-02-09 11:09:23 -0500: HTTP parse error, malformed request (): #<Puma::HttpParserError: Invalid HTTP format, parsing fails.>
2017-02-09 11:09:23 -0500: ENV: {"rack.version"=>[1, 3], "rack.errors"=>#<IO:<STDERR>>, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "SCRIPT_NAME"=>"", "QUERY_STRING"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"puma 3.7.0 Snowy Sagebrush", "GATEWAY_INTERFACE"=>"CGI/1.2"}
@jcberthon

This comment has been minimized.

Copy link

commented Feb 17, 2017

Using triple DES (des3) for encrypting the private key is very deprecated, it is easy to crack. So you either encrypt it with a safer cipher, or you do not encrypt it (remove the -des3 flags) because you anyway need it in clear for puma.

If you want to use better encryption because you are anyway going to securely store the clear-text version needed by puma, replace -des3 by -aes128 (or other ciphers).

@daniel-g

This comment has been minimized.

Copy link

commented Feb 18, 2017

If you experience a problem of malformed request, just configure ssl on puma config file:

# if you are not using rails, just remove this conditional
if ENV.fetch("RAILS_ENV") == 'development'
  ssl_bind '127.0.0.1', '3000', {
    key: ENV.fetch("SSL_KEY_PATH"),
    cert: ENV.fetch("SSL_CERT_PATH"),
    verify_mode: 'none'
  }
end
@JohnSmall

This comment has been minimized.

Copy link

commented Apr 20, 2017

Mmm, none of this is working for me.

I'm on Mac 10.12.4, using Rails 5.0.2, with Puma Version 3.8.2 (ruby 2.4.0-p0), codename: Sassy Salamander

Using the instructions from 1-6 I set things up just fine.

I did manage to do item 7, but it wasn't a drag and drop operation. Drag and drop for the *.crt file wasn't working so I had to click on the file and then it asked me if I wanted to load it into the key chain, and it put it in, but without me having any control over where it went and with no option to define the trust settings. It is in System certs though.

But Chrome still won't trust the cert. So I just opted for 'proceed with caution' and got through to local host. I can see the home page on my site, but any other page just times out, and I see a lot of errors from Puma.

So I tried the suggestion from daniel-g, but that broke things even more, so I now get the error messages that his suggestion was supposed to prevent.

I'll have to find another way to do this.

@ericchen

This comment has been minimized.

Copy link

commented May 15, 2017

On Chrome 58, openssl req -new -key server.key -out server.csr should replace with following command

openssl req -new -key server.key
-x509
-nodes
-new
-out server.crt
-subj /CN=localhost.ssl
-reqexts SAN
-extensions SAN
-config <(cat /System/Library/OpenSSL/openssl.cnf
<(printf '[SAN]\nsubjectAltName=DNS:localhost.ssl'))
-sha256
-days 3650

@fifiteen82726

This comment has been minimized.

Copy link

commented Jun 9, 2017

After I run this script, I encounter this error when I was using rails server.

screen shot 2017-06-09 at 2 27 06 pm

@duskhacker

This comment has been minimized.

Copy link

commented Jun 9, 2017

@fifiteen82726 You are running webrick, not puma, these instructions will not work for webrick.

@mdchaney

This comment has been minimized.

Copy link

commented Jun 23, 2017

The first two instructions here are extraneous. If you simply eliminate the "-des3" in the first statement then there's no need to remove the passphrase later.

  1. openssl genrsa -out server.key 4096
  2. openssl req -new -key server.key -out server.csr
  3. openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

You can do it in a single command if you like, instructions here:

https://www.ibm.com/support/knowledgecenter/en/SSWHYP_4.0.0/com.ibm.apimgmt.cmc.doc/task_apionprem_gernerate_self_signed_openSSL.html

As above, to make Puma listen on 3001 for an SSL connection, add this to your config/puma.rb file:

# On development, run ssl server on port 3001
if ENV.fetch("RAILS_ENV") == 'development'
  ssl_bind '127.0.0.1', '3001', {
    key: ENV.fetch("SSL_KEY_PATH"),
    cert: ENV.fetch("SSL_CERT_PATH"),
    verify_mode: 'none'
  }
end

Make sure to set up environment variables for SSL_KEY_PATH and SSL_CERT_PATH, or replace "ENV.fetch..." above with actual paths.

@craineum

This comment has been minimized.

Copy link

commented Jul 19, 2017

Okay, after much fussing about I got this working. It took two things from the original doc:

  1. @ericchen post got the ssl errors to go away. This should have been it, but then
  2. SSL was crashing puma because of: puma/puma#1214, so I ended running off master until they get this merged in.
@daya

This comment has been minimized.

Copy link

commented Aug 2, 2017

@craineum would you mind sharing your solution I am having exact same problem on

  • Mac OS Sierra 10.12.6 (16G29)
  • Rails 5.0.1

I have tried running puma off of master along with @mdchaney and @ericchen steps ... but I continue to get the same error

2017-08-01 22:34:10 -0500: HTTP parse error, malformed request (): #<Puma::HttpParserError: Invalid HTTP format, parsing fails.>

@Casual3498

This comment has been minimized.

Copy link

commented Sep 28, 2017

Thank you!
On my elementaryOS (ubuntu 16.04) worked.
Only I replace "/private/etc/hosts" on "/etc/hosts"
and "/Users/user_name" on "/home/user_name"

@joelvh

This comment has been minimized.

Copy link

commented Oct 4, 2017

@timoschilling any luck binding SSL and TCP both to the same port? I'm trying to get Rails' force_ssl to redirect from HTTP to HTTPS, but that would mean it's on the same port. This is for local only.

Thanks!

@yuri-zubov

This comment has been minimized.

Copy link

commented Dec 12, 2017

mkdir config/certs && touch config/certs/.keep

puma.rb

if Rails.env.development?
  unless File.exist?(Rails.root.join('config', 'certs', 'localhost.key'))
    def generate_root_cert(root_key)
      root_ca = OpenSSL::X509::Certificate.new
      root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
      root_ca.serial = 0x0
      root_ca.subject = OpenSSL::X509::Name.parse "/C=BE/O=A1/OU=A/CN=localhost"
      root_ca.issuer = root_ca.subject # root CA's are "self-signed"
      root_ca.public_key = root_key.public_key
      root_ca.not_before = Time.now
      root_ca.not_after = @root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
      root_ca.sign(root_key, OpenSSL::Digest::SHA256.new)
      root_ca
    end

    root_key = OpenSSL::PKey::RSA.new(2048)
    file = File.new( Rails.root.join('config', 'certs', 'localhost.key'), "wb")
    file.write(root_key)
    file.close

    root_cert = generate_root_cert(root_key)

    file = File.new( Rails.root.join('config','certs', 'localhost.cert'), "wb")
    file.write(root_cert)
    file.close
  end

  ssl_bind '0.0.0.0', '8443', {
      key: Rails.root.join('config','certs', 'localhost.key'),
      cert: Rails.root.join('config','certs', 'localhost.cert')
  }
end

.gitignore

/config/certs/*
!/config/certs/.keep
@Petercopter

This comment has been minimized.

Copy link

commented Dec 22, 2017

@yuri-zubov Great! I think you might have won this thread! Your solution works for me.

Minor change:

root_ca.not_after = @root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity

becomes

root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity

It's not an instance variable, it's a local variable. Thanks!

@Petercopter

This comment has been minimized.

Copy link

commented Dec 24, 2017

There appears to be some kind of issue with Puma 3.11 and localhost SSL. Going back to 3.10 for now doesn't display the same error.
puma/puma#1483

@rajeshm15

This comment has been minimized.

Copy link

commented Mar 22, 2018

@yuri-zubov @Petercopter Thanks. Only solution that works for me after trying many different things.
While everything seems to work fine, puma generates this error message:
"peer cert: , #<Puma::MiniSSL::SSLError: System error: Success - 0>"
I'm using Puma 3.10.0, Rails 5.2.0.rc1 and Ruby 2.5.

@benjam1n

This comment has been minimized.

Copy link

commented Apr 4, 2018

@Petercopter @rajeshm15 I was getting the same error and then tried opening the whole URL displayed after starting the server:
https://localhost:8443/?cert=/Users/benjam1n/Development/testing/config/certs/localhost.cert&key=/Users/benjam1n/Development/testing/config/certs/localhost.key&verify_mode=none
...and it worked 👍

@jarvisjohnson

This comment has been minimized.

Copy link

commented Apr 15, 2018

Worth noting you can definitely just run the rails server in this fashion, so you still get the rails logs without any extra configuration:
rails s -b 'ssl://127.0.0.1:3000?key=/Users/{{username}}/.ssh/server.key&cert=/Users/{{username}}/.ssh/server.crt'

@djadma

This comment has been minimized.

Copy link

commented Apr 17, 2018

Hello Guys,
I'm getting the error when I run the openssl req -new -key server.key -out server.csr
Error Loading extension section v3_ca

140274425202328:error:22075075:X509 V3 routines:v2i_GENERAL_NAME_ex:unsupported option:v3_alt.c:550:name=subjectKeyIdentifier
140274425202328:error:22098080:X509 V3 routines:X509V3_EXT_nconf:error in extension:v3_conf.c:95:name=subjectAltName, value=@alt_names

Also, getting when run rails server
2018-04-17 15:16:36 +0530: SSL error, peer: 127.0.0.1, peer cert: , #<Puma::MiniSSL::SSLError: OpenSSL error: error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher - 336109761>

@Bodacious

This comment has been minimized.

Copy link

commented Apr 18, 2018

I'm also seeing the above error reported by @djadma. This was working for me for months until today.

@stiller-leser

This comment has been minimized.

Copy link

commented May 23, 2018

There's a small issue in @yuri-zubov's awesome gist. It has to be root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity, not root_ca.not_after = @root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity - without the @.

Below is a version adapted for the use with Puma, but without Rails (e.g. for Grape):

localhost_key = "#{Dir.pwd}/#{File.join('config', 'certs', 'localhost.key')}"
localhost_crt = "#{Dir.pwd}/#{File.join('config', 'certs', 'localhost.crt')}"

unless File.exist?(localhost_key)
  def generate_root_cert(root_key)
    root_ca = OpenSSL::X509::Certificate.new
    root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
    root_ca.serial = 0x0
    root_ca.subject = OpenSSL::X509::Name.parse "/C=BE/O=A1/OU=A/CN=localhost"
    root_ca.issuer = root_ca.subject # root CA's are "self-signed"
    root_ca.public_key = root_key.public_key
    root_ca.not_before = Time.now
    root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
    root_ca.sign(root_key, OpenSSL::Digest::SHA256.new)
    root_ca
  end

  root_key = OpenSSL::PKey::RSA.new(2048)
  file = File.new(localhost_key, "wb")
  file.write(root_key)
  file.close

  root_cert = generate_root_cert(root_key)

  file = File.new(localhost_crt, "wb")
  file.write(root_cert)
  file.close
end

# To be able to use rake etc
if self.respond_to?(:ssl_bind)
  ssl_bind '0.0.0.0', '8443', {
    key: localhost_key,
    cert: localhost_crt
  }
end
@mingca

This comment has been minimized.

Copy link

commented May 25, 2018

This works. But when I am trying to access assets in sidekiq it throws Openssl error.

open('https://localhost:3000/uploads/messasge_attachments/sms/cabff65057e09a8f.').read
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed
	from (irb):6
@webdevotion

This comment has been minimized.

Copy link

commented Jul 10, 2018

@stiller-leser's solution worked for me.

Thanks everyone who chimed in with their solutions and feedback.

  • puma (3.11.4)
  • rails 5.2.0
  • ruby 2.5
  • dockerized environment

My puma.rb:

# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked webserver processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart


if Rails.env.development?

  localhost_key = "#{Dir.pwd}/#{File.join('config', 'certs', 'localhost.key')}"
  localhost_cert = "#{Dir.pwd}/#{File.join('config', 'certs', 'localhost.crt')}"

  unless File.exist?(localhost_key)
    def generate_root_cert(root_key)
      root_ca = OpenSSL::X509::Certificate.new
      root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
      root_ca.serial = 0x0
      root_ca.subject = OpenSSL::X509::Name.parse "/C=BE/O=A1/OU=A/CN=localhost"
      root_ca.issuer = root_ca.subject # root CA's are "self-signed"
      root_ca.public_key = root_key.public_key
      root_ca.not_before = Time.now
      root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
      root_ca.sign(root_key, OpenSSL::Digest::SHA256.new)
      root_ca
    end

    root_key = OpenSSL::PKey::RSA.new(2048)
    file = File.new( localhost_key, "wb")
    file.write(root_key)
    file.close

    root_cert = generate_root_cert(root_key)
    file = File.new( localhost_cert, "wb")
    file.write(root_cert)
    file.close
  end

  ssl_bind '0.0.0.0', '8443', {
    key: localhost_key,
    cert: localhost_cert
  }
end
@gugat

This comment has been minimized.

Copy link

commented Sep 20, 2018

To fix "Your connection is not private" for Google Chrome, allow invalid certificates for resources loaded from localhost:

chrome://flags/#allow-insecure-localhost

image

@scottjacobsen

This comment has been minimized.

Copy link

commented May 24, 2019

There is a fantastic tool called mkcert which eliminates most of the pain of generating self signed certs and installing them as trusted certs on your machine - https://github.com/FiloSottile/mkcert. Way easier than trying wrangle OpenSSL commands and APIs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.