-
Star
(296)
You must be signed in to star a gist -
Fork
(106)
You must be signed in to fork a gist
-
-
Save mikhailov/3052776 to your computer and use it in GitHub Desktop.
| # Nginx+Unicorn best-practices congifuration guide. Heartbleed fixed. | |
| # We use latest stable nginx with fresh **openssl**, **zlib** and **pcre** dependencies. | |
| # Some extra handy modules to use: --with-http_stub_status_module --with-http_gzip_static_module | |
| # | |
| # Deployment structure | |
| # | |
| # SERVER: | |
| # /etc/init.d/nginx (1. nginx) | |
| # /home/app/public_html/app_production/current (Capistrano directory) | |
| # | |
| # APP: | |
| # config/server/production/nginx.conf (2. nginx.conf) | |
| # config/server/production/nginx_host.conf (3. nginx_host.conf) | |
| # config/server/production/nginx_errors.conf (4. nginx_errors.conf) | |
| # config/deploy.rb (5. deploy.rb) | |
| # config/deploy/production.rb (6. production.rb) | |
| # config/server/production/unicorn.rb (7. unicorn.rb) | |
| # config/server/production/unicorn_init.sh (8. unicorn_init.sh) | |
| cd /usr/src | |
| wget http://nginx.org/download/nginx-1.5.13.tar.gz | |
| tar xzvf ./nginx-1.5.13.tar.gz && rm -f ./nginx-1.5.13.tar.gz | |
| wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.32.tar.gz | |
| tar xzvf pcre-8.32.tar.gz && rm -f ./pcre-8.32.tar.gz | |
| wget http://www.openssl.org/source/openssl-1.0.1g.tar.gz | |
| tar xzvf openssl-1.0.1g.tar.gz && rm -f openssl-1.0.1g.tar.gz | |
| cd nginx-1.5.13 | |
| ./configure --prefix=/opt/nginx --with-pcre=/usr/src/pcre-8.32 --with-openssl-opt=no-krb5 --with-openssl=/usr/src/openssl-1.0.1g --with-http_ssl_module --with-http_spdy_module --without-mail_pop3_module --without-mail_smtp_module --without-mail_imap_module --with-http_stub_status_module --with-http_gzip_static_module | |
| make && make install | |
| mkdir /tmp/client_body_temp | |
| mkdir /opt/nginx/ssl_certs | |
| echo "include /home/app/public_html/app_production/current/config/server/production/nginx.conf;" > /opt/nginx/conf/nginx.conf | |
| vim /etc/init.d/nginx # see the config below | |
| chmod +x /etc/init.d/nginx && update-rc.d -f nginx defaults |
| #! /bin/sh | |
| ### BEGIN INIT INFO | |
| # Provides: nginx | |
| # Required-Start: $all | |
| # Required-Stop: $all | |
| # Default-Start: 2 3 4 5 | |
| # Default-Stop: 0 1 6 | |
| # Short-Description: starts the nginx web server | |
| # Description: starts nginx using start-stop-daemon | |
| ### END INIT INFO | |
| # | |
| # /etc/init.d/nginx | |
| PATH=/opt/nginx/sbin:/sbin:/bin:/usr/sbin:/usr/bin | |
| DAEMON=/opt/nginx/sbin/nginx | |
| NAME=nginx | |
| DESC=nginx | |
| test -x $DAEMON || exit 0 | |
| # Include nginx defaults if available | |
| if [ -f /etc/default/nginx ] ; then | |
| . /etc/default/nginx | |
| fi | |
| set -e | |
| case "$1" in | |
| start) | |
| echo -n "Starting $DESC: " | |
| start-stop-daemon --start --quiet --pidfile /opt/nginx/logs/$NAME.pid \ | |
| --exec $DAEMON -- $DAEMON_OPTS | |
| echo "$NAME." | |
| ;; | |
| stop) | |
| echo -n "Stopping $DESC: " | |
| start-stop-daemon --stop --quiet --pidfile /opt/nginx/logs/$NAME.pid \ | |
| --exec $DAEMON | |
| echo "$NAME." | |
| ;; | |
| restart|force-reload) | |
| echo -n "Restarting $DESC: " | |
| start-stop-daemon --stop --quiet --pidfile \ | |
| /opt/nginx/logs/$NAME.pid --exec $DAEMON | |
| sleep 1 | |
| start-stop-daemon --start --quiet --pidfile \ | |
| /opt/nginx/logs/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS | |
| echo "$NAME." | |
| ;; | |
| reload) | |
| echo -n "Reloading $DESC configuration: " | |
| start-stop-daemon --stop --signal HUP --quiet --pidfile /opt/nginx/logs/$NAME.pid \ | |
| --exec $DAEMON | |
| echo "$NAME." | |
| ;; | |
| *) | |
| N=/etc/init.d/$NAME | |
| echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| exit 0 |
| # Nginx main block configuration file. | |
| # The most important directives here are ssl_protocols and ssl_ciphers | |
| # Keep nginx configuration in repo, then just include it in /opt/nginx/conf/nginx.conf | |
| # And DDoS prevent attack with directive limit_req_zone (limit 10 request/sec from 1 IP address) | |
| # then it enables in the server block by "limit_req zone=one". | |
| # | |
| # config/server/production/nginx.conf | |
| user app; | |
| worker_processes 2; | |
| worker_priority -5; | |
| timer_resolution 100ms; | |
| error_log logs/nginx.error.log; | |
| events { | |
| use epoll; | |
| worker_connections 2048; | |
| } | |
| http { | |
| client_max_body_size 25m; | |
| client_body_buffer_size 128k; | |
| client_body_temp_path /tmp/client_body_temp; | |
| include mime.types; | |
| default_type application/octet-stream; | |
| server_tokens off; | |
| sendfile on; | |
| tcp_nopush on; | |
| tcp_nodelay on; | |
| keepalive_timeout 70; | |
| gzip on; | |
| gzip_http_version 1.1; | |
| gzip_disable "msie6"; | |
| gzip_vary on; | |
| gzip_min_length 1100; | |
| gzip_buffers 64 8k; | |
| gzip_comp_level 3; | |
| gzip_proxied any; | |
| gzip_types text/plain text/css application/x-javascript text/xml application/xml; | |
| ssl_certificate /opt/nginx/ssl_certs/server.crt; | |
| ssl_certificate_key /opt/nginx/ssl_certs/server.key; | |
| ssl_session_timeout 15m; | |
| ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; | |
| ssl_prefer_server_ciphers on; | |
| ssl_session_cache shared:SSL:10m; | |
| ssl_stapling on; | |
| add_header Strict-Transport-Security "max-age=16070400; includeSubdomains"; | |
| add_header X-Frame-Options DENY; | |
| limit_req_zone $binary_remote_addr zone=one:10m rate=50r/s; | |
| include /home/app/public_html/app_production/current/config/server/nginx_host.conf; | |
| } |
| # Nginx server block configuration with proxy_pass to Unicorn upstream | |
| # We use full-SSL site with web-server redirection, no mess with Rails application redirection | |
| # | |
| # config/server/production/nginx_host.conf | |
| upstream unicorn { | |
| server unix:/tmp/unicorn.production.sock fail_timeout=0; | |
| } | |
| server { | |
| listen 80; | |
| server_name server.com; | |
| rewrite ^(.*) https://$host$1 permanent; | |
| location ~ \.(php|html)$ { | |
| deny all; | |
| } | |
| access_log /dev/null; | |
| error_log /dev/null; | |
| } | |
| server { | |
| ssl on; | |
| listen 443 spdy ssl; | |
| server_name server.com; | |
| root /home/app/public_html/app_production/current/public; | |
| try_files $uri /system/maintenance.html @unicorn; | |
| location @unicorn { | |
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |
| proxy_set_header X-Forwarded-Proto $scheme; | |
| proxy_set_header Host $http_host; | |
| proxy_redirect off; | |
| proxy_pass http://unicorn; | |
| limit_req zone=one burst=15; | |
| access_log /dev/null; | |
| error_log logs/unicorn.error.log; | |
| } | |
| location ~ ^/(assets|images|javascripts|stylesheets|swfs|system)/ { | |
| gzip_static on; | |
| expires max; | |
| add_header Cache-Control public; | |
| add_header Last-Modified ""; | |
| add_header ETag ""; | |
| } | |
| include /home/app/public_html/app_production/current/config/server/production/nginx_errors.conf; | |
| access_log /dev/null; | |
| error_log /dev/null; | |
| } |
| # nginx configuration piece to handle errorrs | |
| # | |
| # config/server/production/nginx_errors.conf | |
| error_page 500 502 504 /500.html; | |
| error_page 503 @503; | |
| location = /50x.html { | |
| root html; | |
| } | |
| location = /404.html { | |
| root html; | |
| } | |
| location @503 { | |
| error_page 405 = /system/maintenance.html; | |
| if (-f $document_root/system/maintenance.html) { | |
| rewrite ^(.*)$ /system/maintenance.html break; | |
| } | |
| rewrite ^(.*)$ /503.html break; | |
| } | |
| if ($request_method !~ ^(GET|HEAD|PUT|POST|DELETE|OPTIONS)$ ){ | |
| return 405; | |
| } | |
| if (-f $document_root/system/maintenance.html) { | |
| return 503; | |
| } | |
| location ~ \.(php|html)$ { | |
| return 405; | |
| } |
| # Capistrano configuration. Now with TRUE zero-downtime unless DB migration. | |
| # | |
| # require 'new_relic/recipes' - Newrelic notification about deployment | |
| # require 'capistrano/ext/multistage' - We use 2 deployment environment: staging and production. | |
| # set :deploy_via, :remote_cache - fetch only latest changes during deployment | |
| # set :normalize_asset_timestamps - no need to touch (date modification) every assets | |
| # "deploy:web:disable" - traditional maintenance page (during DB migrations deployment) | |
| # task :restart - Unicorn with preload_app should be reloaded by USR2+QUIT signals, not HUP | |
| # | |
| # http://unicorn.bogomips.org/SIGNALS.html | |
| # "If “preload_app” is true, then application code changes will have no effect; | |
| # USR2 + QUIT (see below) must be used to load newer code in this case" | |
| # | |
| # config/deploy.rb | |
| require 'bundler/capistrano' | |
| require 'capistrano/ext/multistage' | |
| require 'new_relic/recipes' | |
| set :stages, %w(staging production) | |
| set :default_stage, "staging" | |
| set :scm, :git | |
| set :repository, "..." | |
| set :deploy_via, :remote_cache | |
| default_run_options[:pty] = true | |
| set :application, "app" | |
| set :use_sudo, false | |
| set :user, "app" | |
| set :normalize_asset_timestamps, false | |
| before "deploy", "deploy:delayed_job:stop" | |
| before "deploy:migrations", "deploy:delayed_job:stop" | |
| after "deploy:update_code", "deploy:symlink_shared" | |
| before "deploy:migrate", "deploy:web:disable", "deploy:db:backup" | |
| after "deploy", "newrelic:notice_deployment", "deploy:cleanup", "deploy:delayed_job:restart" | |
| after "deploy:migrations", "deploy:web:enable", "newrelic:notice_deployment", "deploy:cleanup", "deploy:delayed_job:restart" | |
| namespace :deploy do | |
| %w[start stop].each do |command| | |
| desc "#{command} unicorn server" | |
| task command, :roles => :app, :except => { :no_release => true } do | |
| run "#{current_path}/config/server/#{rails_env}/unicorn_init.sh #{command}" | |
| end | |
| end | |
| desc "restart unicorn server" | |
| task :restart, :roles => :app, :except => { :no_release => true } do | |
| run "#{current_path}/config/server/#{rails_env}/unicorn_init.sh upgrade" | |
| end | |
| desc "Link in the production database.yml and assets" | |
| task :symlink_shared do | |
| run "ln -nfs #{deploy_to}/shared/config/database.yml #{release_path}/config/database.yml" | |
| end | |
| namespace :delayed_job do | |
| desc "Restart the delayed_job process" | |
| task :restart, :roles => :app, :except => { :no_release => true } do | |
| run "cd #{current_path}; RAILS_ENV=#{rails_env} bundle exec script/delayed_job restart" rescue nil | |
| end | |
| desc "Stop the delayed_job process" | |
| task :stop, :roles => :app, :except => { :no_release => true } do | |
| run "cd #{current_path}; RAILS_ENV=#{rails_env} bundle exec script/delayed_job stop" rescue nil | |
| end | |
| end | |
| namespace :db do | |
| desc "backup of database before migrations are invoked" | |
| task :backup, :roles => :db, :only => { :primary => true } do | |
| filename = "#{deploy_to}/shared/db_backup/#{stage}_db.#{Time.now.utc.strftime("%Y-%m-%d_%I:%M")}_before_deploy.gz" | |
| text = capture "cat #{deploy_to}/current/config/database.yml" | |
| yaml = YAML::load(text)["#{stage}"] | |
| on_rollback { run "rm #{filename}" } | |
| run "mysqldump --single-transaction --quick -u#{yaml['username']} -h#{yaml['host']} -p#{yaml['password']} #{yaml['database']} | gzip -c > #{filename}" | |
| end | |
| end | |
| namespace :web do | |
| desc "Maintenance start" | |
| task :disable, :roles => :web do | |
| on_rollback { run "rm #{shared_path}/system/maintenance.html" } | |
| page = File.read("public/503.html") | |
| put page, "#{shared_path}/system/maintenance.html", :mode => 0644 | |
| end | |
| desc "Maintenance stop" | |
| task :enable, :roles => :web do | |
| run "rm #{shared_path}/system/maintenance.html" | |
| end | |
| end | |
| end | |
| namespace :log do | |
| desc "A pinch of tail" | |
| task :tailf, :roles => :app do | |
| run "tail -n 10000 -f #{shared_path}/log/#{rails_env}.log" do |channel, stream, data| | |
| puts "#{data}" | |
| break if stream == :err | |
| end | |
| end | |
| end |
| # capistrano production config | |
| # | |
| # config/deploy/production.rb | |
| server "8.8.8.8", :app, :web, :db, :primary => true | |
| set :branch, "production" | |
| set :deploy_to, "/home/app/public_html/app_production" | |
| set :rails_env, "production" |
| # Unicorn configuration file to be running by unicorn_init.sh with capistrano task | |
| # read an example configuration before: http://unicorn.bogomips.org/examples/unicorn.conf.rb | |
| # | |
| # working_directory, pid, paths - internal Unicorn variables must to setup | |
| # worker_process 4 - is good enough for serve small production application | |
| # timeout 30 - time limit when unresponded workers to restart | |
| # preload_app true - the most interesting option that confuse a lot of us, | |
| # just setup is as true always, it means extra work on | |
| # deployment scripts to make it correctly | |
| # BUNDLE_GEMFILE - make Gemfile accessible with new master | |
| # before_fork, after_fork - reconnect to all dependent services: DB, Redis, Sphinx etc. | |
| # deal with old_pid only if CPU or RAM are limited enough | |
| # | |
| # config/server/production/unicorn.rb | |
| app_path = "/home/app/public_html/app_production/current" | |
| working_directory "#{app_path}" | |
| pid "#{app_path}/tmp/pids/unicorn.pid" | |
| stderr_path "#{app_path}/log/unicorn.log" | |
| stdout_path "#{app_path}/log/unicorn.log" | |
| listen "/tmp/unicorn.production.sock" | |
| worker_processes 4 | |
| timeout 30 | |
| preload_app true | |
| before_exec do |server| | |
| ENV["BUNDLE_GEMFILE"] = "#{app_path}/Gemfile" | |
| end | |
| before_fork do |server, worker| | |
| if defined?(ActiveRecord::Base) | |
| ActiveRecord::Base.connection.disconnect! | |
| end | |
| if defined?(Resque) | |
| Resque.redis.quit | |
| end | |
| sleep 1 | |
| end | |
| after_fork do |server, worker| | |
| if defined?(ActiveRecord::Base) | |
| ActiveRecord::Base.establish_connection | |
| end | |
| if defined?(Resque) | |
| Resque.redis = 'localhost:6379' | |
| end | |
| end |
| # Unicorn handle shell script | |
| # | |
| # APP_ROOT, PID - are the same as you setup above | |
| # CMD - use bundle binstubs (bundle install --binstubs) to | |
| # forget about "bundle exec" stuff, run in demonize mode | |
| # bin/unicorn is for Rack application (config.ru in root dir), but | |
| # bin/unicorn_rails is to use with Rails 2.3 | |
| # | |
| # To handle "app_preload true" configuration we should use USR2+QUIT signals, not HUP! | |
| # So we rewrite capistrano deployment scripts to manage it. | |
| # | |
| # config/server/production/unicorn_init.sh | |
| #!/bin/sh | |
| set -e | |
| # Example init script, this can be used with nginx, too, | |
| # since nginx and unicorn accept the same signals | |
| TIMEOUT=${TIMEOUT-60} | |
| APP_ROOT=/home/app/public_html/app_production/current | |
| PID=$APP_ROOT/tmp/pids/unicorn.pid | |
| CMD="$APP_ROOT/bin/unicorn -D -c $APP_ROOT/config/server/unicorn.rb -E production" | |
| action="$1" | |
| set -u | |
| old_pid="$PID.oldbin" | |
| cd $APP_ROOT || exit 1 | |
| sig () { | |
| test -s "$PID" && kill -$1 `cat $PID` | |
| } | |
| oldsig () { | |
| test -s $old_pid && kill -$1 `cat $old_pid` | |
| } | |
| case $action in | |
| start) | |
| sig 0 && echo >&2 "Already running" && exit 0 | |
| $CMD | |
| ;; | |
| stop) | |
| sig QUIT && exit 0 | |
| echo >&2 "Not running" | |
| ;; | |
| force-stop) | |
| sig TERM && exit 0 | |
| echo >&2 "Not running" | |
| ;; | |
| restart|reload) | |
| sig HUP && echo reloaded OK && exit 0 | |
| echo >&2 "Couldn't reload, starting '$CMD' instead" | |
| $CMD | |
| ;; | |
| upgrade) | |
| if sig USR2 && sleep 15 && sig 0 && oldsig QUIT | |
| then | |
| n=$TIMEOUT | |
| while test -s $old_pid && test $n -ge 0 | |
| do | |
| printf '.' && sleep 1 && n=$(( $n - 1 )) | |
| done | |
| echo | |
| if test $n -lt 0 && test -s $old_pid | |
| then | |
| echo >&2 "$old_pid still exists after $TIMEOUT seconds" | |
| exit 1 | |
| fi | |
| exit 0 | |
| fi | |
| echo >&2 "Couldn't upgrade, starting '$CMD' instead" | |
| $CMD | |
| ;; | |
| reopen-logs) | |
| sig USR1 | |
| ;; | |
| *) | |
| echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>" | |
| exit 1 | |
| ;; | |
| esac |
The init.d scripts start nginx and unicorn but cannot perform the other actions (e.g. stop). Not sure if this is Ubuntu specific or not, but start-stop-daemon will not create a pid file without the flag --make-pid - and even then will write the wrong pid due to the nginx/unicorn processes forking from the main process.
Здравствуйте. Сделал как написано в вашем конфиге, но у меня не стартуют рельсы.
В логах nginx все норм, а вот в логах unicorn.error.log данное сообщение
2013/03/26 15:49:41 [error] 25371#0: *8 limiting requests, excess: 1.000 by zone "one", client: 192.168.91.185, server: localhost, request: "GET / HTTP/1.1", host: "192.168.91.15"
А сама страница отдает 404 Ошибку
Very good. Please change the comment: (limit 10 request/sec from 1 IP address) to (limit 50...) about nginx limit_req_zone.
@k3NGuru по-моему, limit_req_zone был поломан и его починили только несколько месяцев назад, пробуйте стартануть nginx без него
@mikhailov Все равно не заводится система. Уже сделал как написано тут https://coderwall.com/p/8igwqa настроил как написано в данном конфиге, Вот моя папка https://github.com/k3NGuru/mlvz
2013/04/05 16:02:07 [error] 19467#0: *2 limiting requests, excess: 1.000 by zone "one", client: 192.168.91.163, server: 192.168.91.15, request: "GET / HTTP/1.1^@^Dho^@^@", host: "192.168.91.15"
192.168.91.15 - Сервер
192.168.91.163 - Клиент
SSL настроил как написано тут http://wiki.nginx.org/HttpSslModule
@k3NGuru все дело в специфичной работе встроенного модуля limit_req, добавь burst=5 для решения проблемы:
limit_req zone=one burst=5;
Это пропустит 6 запросов в секунду, надо больше с одной айпи - увеличивай burst, либо распараллель нагрузку между несколькими Nginx серверами. Почему так, а не иначе, попытались объяснить здесь: http://www.lexa.ru/nginx-ru/msg38569.html Сегодня задан вопрос в nginx-рассылку.
Thank you for sharing this. :-)
Amazing. Thank you very much man =)
Great stuff to ease nginx usage!
A few queries/points:
- I needed to add ' application/javascript' to gzip_types, else js was not getting compressed. Added that before application/x-javascript. Any reason why you don't have that?
- keepalive_timeout of 70 seems to be a very very high value. Isn't it better to control it within 2/3 (as otherwise it's easy for hackers to drown the server with open files)
require 'capistrano/ext/multistage'Данное расширение объединено с основной веткой Capistrano capistrano/capistrano@eed1a6e. Можно исключить из конфига.
@xymbol master process runs as root, but workers as app