Search code examples
ruby-on-railsdeploymentunicornproductionrvm-capistrano

Rails in production throws 502 error


I try to deploy a testing application in production mode to VPN using:

  • new and clean droplet on Digital Ocean
  • Ubuntu 14.10
  • Rails 4.2.0
  • Ruby 2.1.5
  • RVM
  • PostgreSQL
  • Unicorn
  • Capistrano 3.2.1
  • Nginx

My app was deployed successfully, but it threw 500.html.

The app had made migration and got assets in public directory. There was no errors during deployment and in logs (production.log, unicorn_error.log, unicorn.log). I checked unicorn's workers by ps aux|grep unicorn and there was several workers working on it. Rails console in production worked too, so there was the connection with PostgreSQL.

I tried to start my app in production on the local PC and it worked with rake RAILS_ENV=production assets:precompile.

Help me to find out the problem, please. How can I detect what the reason of the problem is? Is it in my deploying code or in the settings of the server?

Nginx settings /etc/nginx/sites-available/default

upstream unicorn {
  server unix:/tmp/unicorn.app.sock fail_timeout=0;
}

server {
        listen 80 default deferred;
        #server_name example.com;
        root /var/www/app/current/public;

        location ^~ /assets/ {
                gzip_static on;
                expires max;
                add_header Cache_Control public;
                #try_files $uri/index.html $uri.html $uri @app;
        }

        try_files $uri/index.html $uri @unicorn;

        location @unicorn {
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                proxy_pass http://unicorn;
        }
        error_page 500 502 503 504 /500.html;
        keepalive_timeout 10;
}

In Gemfile I use

group :development do
  gem 'capistrano', '3.2.1', require: false
  gem 'capistrano-rails', require: false
  gem 'capistrano-bundler', require: false
  gem 'rvm1-capistrano3', require: false
  gem 'capistrano3-unicorn', require: false
end

group :production do
  gem 'unicorn'
end

/config/deploy.rb

set :application, 'app'
set :user, "XXX"

set :scm, :git
#set :branch, ->{ `git rev-parse --abbrev-ref HEAD`.chomp }
set :branch, "master"
set :repo_name, 'deployment'
set :repo_url, ->{ "[email protected]:sfolt/#{fetch :repo_name}.git" }

set :rails_env, fetch(:stage)
set :rvm1_ruby_version, 'ruby-2.1.5'

set :keep_releases, 5
set :format, :pretty
set :use_sudo, false
set :deploy_via, :remote_cache

set :unicorn_conf, "#{deploy_to}/current/config/unicorn.rb"
set :unicorn_pid, "#{deploy_to}/shared/pids/unicorn.pid"
set :bundle_without, [:development, :test]

set :linked_files, %w{
  config/database.yml
  config/secrets.yml
}
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets tmp/sessions}

namespace :assets do
  task :precompile do
    run "cd #{release_path}; rake assets:precompile RAILS_ENV=production"
  end
end

namespace :deploy do
  task :restart do
    run "if [ -f #{unicorn_pid} ] && [ -e /proc/$(cat #{unicorn_pid}) ]; then kill -USR2 `cat #{unicorn_pid}`; else cd #{deploy_to}/current && bundle exec unicorn -c #{unicorn_conf} -E #{rails_env} -D; fi"
  end
  task :start do
    run "bundle exec unicorn -c #{unicorn_conf} -E #{rails_env} -D"
  end
  task :stop do
    run "if [ -f #{unicorn_pid} ] && [ -e /proc/$(cat #{unicorn_pid}) ]; then kill -QUIT `cat #{unicorn_pid}`; fi"
  end
end

after 'deploy:finishing', 'deploy:cleanup'
after 'deploy:publishing', 'unicorn:restart'

/config/deploy/production.rb

set :stage, :production
set :deploy_to, "/var/www/app"

server 'XXX.XXX.XXX.XXX',
  user: 'XXX',
  port: XXX,
  roles: [:app, :web, :db],
  ssh_options: {
    user: 'XXX'
  }

/config/unicorn/production.rb

deploy_to  = "/var/www/app"
rails_root = "#{deploy_to}/current"
pid_file   = "#{deploy_to}/shared/pids/unicorn.pid"
socket_file= "#{deploy_to}/shared/unicorn.sock"
log_file   = "#{rails_root}/log/unicorn.log"
err_log    = "#{rails_root}/log/unicorn_error.log"
old_pid    = pid_file + '.oldbin'

timeout 120
worker_processes 2
listen socket_file, backlog: 1024
pid pid_file
stderr_path err_log
stdout_path log_file

preload_app true

GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "#{rails_root}/Gemfile"
end

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and
  ActiveRecord::Base.connection.disconnect!

  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and
  ActiveRecord::Base.establish_connection
end

Solution

  • The 502 error means there's no communication between nginx and your unicorn process.

    Upon examining your config files, it seems to me that the issue is that you set unicorn to listen on this socket /var/www/app/shared/unicorn.sock, but you tell nginx to communicate with unicorn through this socket /tmp/unicorn.app.sock

    Changing your upstream portion on your nginx.conf file as follows should fix the issue:

    upstream unicorn {
      server unix:/var/www/app/shared/unicorn.sock fail_timeout=0;
    }