I'm trying to configure an app on my local development environment to use SSL. I started down this path when I noticed a CORS error upon trying to request data from the back end Rails API. When I try to access the homepage at https://localhost:3001, I receive the following message:
Secure Connection Failed
An error occurred during a connection to localhost:3001. SSL receiveda record that exceeded the maximum permissible length.
Error code: SSL_ERROR_RX_RECORD_TOO_LONG
The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.Please contact the website owners to inform them of this problem.
I am also seeing the following output:
06:25:30 web.1 | 2024-09-15 06:25:30 -0400 HTTP parse error, malformed request: #<Puma::HttpParserError: Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?>
To run the server locally I typically run bin/dev
. This is the script for that:
#!/usr/bin/env shif gem list --no-installed --exact --silent foreman; then echo "Installing foreman..." gem install foremanfi# Default to port 3000 if not specifiedexport PORT="${PORT:-3001}"exec foreman start -f Procfile.dev --env /dev/null "$@"
What's interesting is that when I simply run rails s
, I am able to access the front end at https://localhost:3000
as well return data from the API at https://localhost:3001/api/reports
. This all leads me to think that something is wrong with my Puma configuration.
So far I have done the following to try and get this to work properly:
- regenerated SSL keys/certs for both rails and vite, verified that they are located under
app/config/ssl
- Verified that the keys are added to my keychain
- modified my puma configuration to bind SSL
- set my vite config to use https
- installed rack-cors in the gemfile
- cleared my browser cache, tried accessing localhost via a different browser or incognito mode
- replaced `bin/rails server with
Gemfile:
gem 'rack-cors', '~> 2.0', '>= 2.0.2'
config/puma.rb:
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }threads threads_count, threads_countport ENV.fetch("PORT") { 3000 }if ENV.fetch("RAILS_ENV") == "development" ssl_bind '0.0.0.0', '3001', { key: "config/ssl/localhost.key", cert: "config/ssl/localhost.crt", verify_mode: "none" } puts "Puma running with SSL on https://localhost:3001"else port ENV.fetch("PORT") { 3000 } puts "Puma running on HTTP on port #{ENV.fetch('PORT') { 3000 }}"endenvironment ENV.fetch("RAILS_ENV") { "development" }preload_app!workers ENV.fetch("WEB_CONCURRENCY") { 2 } if ENV.fetch("RAILS_ENV") == "production"on_worker_boot do ActiveRecord::Base.establish_connection if defined?(ActiveRecord)endplugin :tmp_restartpidfile ENV.fetch("PIDFILE") if ENV["PIDFILE"]
Procfile.dev:
web: env RUBY_DEBUG_OPEN=true bin/rails servervite: bin/vite devcss: yarn build:css --watchtypes: yarn ts-check
vite.config.ms:
import { defineConfig } from 'vite';import * as path from 'path';import FullReload from 'vite-plugin-full-reload';import RubyPlugin from 'vite-plugin-ruby';import tailwindcss from 'tailwindcss';import fs from 'fs';export default defineConfig({ css: { postcss: { plugins: [tailwindcss], }, }, plugins: [ RubyPlugin(), FullReload(['config/routes.rb', 'app/views/**/*'], { delay: 250 }), ], resolve: { alias: [ { find: '@/components', replacement: path.resolve(__dirname, './app/frontend/components') }, { find: '@/stylesheets', replacement: path.resolve(__dirname, './app/frontend/stylesheets') }, { find: '@/entrypoints', replacement: path.resolve(__dirname, './app/javascript/entrypoints') }, ], }, server: { https: { key: fs.readFileSync('config/ssl/localhost-vite.key'), cert: fs.readFileSync('config/ssl/localhost-vite.crt'), }, port: 3000, // Vite server port },});
config/initializers/cors.rb:
Rails.application.config.middleware.insert_before 0, Rack::Cors do Rails.logger.info "\n\n\nCORS ENABLED \n\n\n" allow do origins 'https://localhost:3001' # React app URL with HTTPS resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head], credentials: true endend
config/environments/development.rb:
require 'active_support/core_ext/integer/time'Rails.application.configure do Rails.application.reloader.to_prepare do ActiveStorage::Blob end config.enable_reloading = true config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } config.eager_load = false config.consider_all_requests_local = true config.server_timing = true if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store config.public_file_server.headers = {'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false config.cache_store = :null_store end config.active_storage.service = :cloudinary config.action_mailer.raise_delivery_errors = false config.action_mailer.perform_caching = false config.active_support.deprecation = :log config.active_support.disallowed_deprecation = :raise config.active_support.disallowed_deprecation_warnings = [] config.active_record.migration_error = :page_load config.active_record.verbose_query_logs = true config.active_job.verbose_enqueue_logs = true config.assets.quiet = true config.action_view.annotate_rendered_view_with_filenames = true config.action_controller.raise_on_missing_callback_actions = trueend