Fixing Ruby openssl errors

ruby openssl building

The other day I wanted to try out Rails 7’s import maps on my new M1 MacBook, but I was blocked by an error telling me that Ruby couldn’t make any SSL requests.

I’ll go through how I fixed this.

The command I tried that gave me an error was:

bin/importmap pin @picocss/pico

I got the following error about SSL:

/Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/protocol.rb:46:in `connect_nonblock': SSL_connect returned=1 errno=0 peeraddr=52.142.124.215:443 state=error: certificate verify failed (unable to get local issuer certificate) (OpenSSL::SSL::SSLError)
  from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/protocol.rb:46:in `ssl_socket_connect'
  from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/http.rb:1048:in `connect'
  from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/http.rb:976:in `do_start'
  from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/http.rb:965:in `start'
  from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/3.1.0/net/http.rb:1530:in `request'
  from (irb):25:in `<main>'
  from /Users/abuisman/.asdf/installs/ruby/3.1.0/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
  from /Users/abuisman/.asdf/installs/ruby/3.1.0/bin/irb:25:in `load'
  from /Users/abuisman/.asdf/installs/ruby/3.1.0/bin/irb:25:in `<main>'

I could reproduce this in a Ruby console with this code:

require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://duckduckgo.com/")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)

response = http.request(request)
puts response.read_body

I’ve seen this error before and usually it happens when you haven’t compiled ruby properly with openssl. So I first reinstalled openssl@1.1 and openssl@3 and a few times again and again, but I couldn’t fix it.

I was ducking around for some info and I sort of picked up in my searching that this could mean that my CA certificates were outdated.

I’ll start with the fix:

brew uninstall openssl@1.1 --force --ignore-dependencies
brew install openssl@1.1

The --force --ignore-dependencies is critical. I just ran uninstall without it the first 10 times or so and I didn’t notice that nothing was really being removed because of dependencies. It could be that it was clear from the command output but I do not remember seeing anything relating to dependencies. --force --ignore-dependencies was the thing that made a difference for me.

With the following .zshrc configuration for compilation related things:


export OPTFLAGS="-Wno-error=implicit-function-declaration"

# readline
export LDFLAGS="-L/opt/homebrew/opt/readline/lib"
export CPPFLAGS="-I/opt/homebrew/opt/readline/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/readline/lib/pkgconfig"

# openssl config
export PATH="/opt/homebrew/opt/openssl@1.1:$PATH" # Might be overkill
export PATH="/opt/homebrew/opt/openssl@1.1/bin:$PATH"

export LIBRARY_PATH="/opt/homebrew/opt/openssl@1.1:$LIBRARY_PATH"
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=/opt/homebrew/opt/openssl@1.1"
export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib:$LDFLAGS"
export CPPFLAGS="-I/opt/homebrew/opt/openssl@1.1/include:$CPPFLAGS"
export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@1.1/lib/pkgconfig:$PKG_CONFIG_PATH"

# libffi
export LDFLAGS="$LDFLAGS:-L/opt/homebrew/opt/libffi/lib"
export CPPFLAGS="$CPPFLAGS:-I/opt/homebrew/opt/libffi/include"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/opt/homebrew/opt/libffi/lib/pkgconfig"

Then install ruby. For example with asdf:

asdf install ruby 3.1.0

Oh and how do we know it works?

A colleague of mine pointed me to the bundler documentation and there they mention a nice command:

curl -Lks 'https://git.io/rg-ssl' | ruby

Bundler: How to troubleshoot RubyGems and Bundler TLS/SSL Issues

Before the fix the output looked like this:

Brew ssl check shows it is broken.

Afterwards it looks like this:

Brew ssl check shows it is working.

This means we have a quick way to test the config without having to boot up a ruby console.

We have to could go deeper

In my recent migration from an i7 MacBook to this new M1 Pro I moved over my dotfiles with my .zshrc config. I figured it was wrong at some point so I had a critical look. I must admit looked ridiculous:

export OPTFLAGS="-Wno-error=implicit-function-declaration"

# readline
export LDFLAGS="-L/opt/homebrew/opt/readline/lib"
export CPPFLAGS="-I/opt/homebrew/opt/readline/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/readline/lib/pkgconfig"

# openssl config
export PATH="/opt/homebrew/opt/openssl@1.1:$PATH"

export LIBRARY_PATH="/opt/homebrew/opt/openssl@1.1"
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=/opt/homebrew/opt/openssl@1.1"
export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib"
export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@1.1/lib/pkgconfig"

# libffi
export LDFLAGS="-L/opt/homebrew/opt/libffi/lib"
export CPPFLAGS="$-I/opt/homebrew/opt/libffi/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/libffi/lib/pkgconfig"

So many of these variables are being overwritten in every section. It seems like this stuff accumulated while I was trying out things over time or as I was installing libraries. Who knows. Git shows many of these lines as new since moving to the new Mac.

What is the problem? For example LDFLAGS gets overwritten like so:

export LDFLAGS="-L/opt/homebrew/opt/readline/lib"
export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib"
export LDFLAGS="-L/opt/homebrew/opt/libffi/lib"

So in the end the value is just -L/opt/homebrew/opt/libffi/lib. You can see above that I corrected this by appending :$LDFLAGS" each time. This means that it will add things to the LDFLAGS variable instead of overwriting it. It turns out this cleanup didn’t do much, or at least reverting it didn’t break openssl again.

Interestingly, I had already built Ruby 2.7.4 already and it was showing the same issues. After fixing 3.1.0, 2.7.4 was also fixed. I believe this has something to do with new certificate files being downloaded as I mentioned earlier. My general idea is that reinstalling openssl properly also updated some certificate.

If you look here:

Brew ssl check shows it is working.

You can see that there is a certificate directory and a certificate file:

SSL_CERT_FILE:  /opt/homebrew/etc/openssl@1.1/cert.pem
SSL_CERT_DIR:   /opt/homebrew/etc/openssl@1.1/certs

When I look around in the directory it doesn’t contain anything and when I open the certificate file I see the following:

Brew ssl check shows it is working.

I have no clue what this certificate’s role is to be honest. I might find out and update here.

Conclusion

The fix was for me as described above. I ran into several github issues, stack overflows, etc. that allowed me to piece together these instructions. I hope my gathering of these solutions will be the real fix for you, or for me when I run into this again and forget.