Selected VPS: Linode, 1GB Ram, 20 GB SSD, 1 TB transfer
OS: Ubuntu 18.04 LTS
App server: Passenger
Web Server: Ngnix
If you’d like to try Linode, I would greatly appreciate using this referral link – Linode: SSD Cloud Hosting & Linux Servers
Guide
First step – boot your server from Linode Manager Dashboard and ssh into it
General Server Updates
apt-get update && apt-get upgrade #If desired to run auto-updates apt-get install unattended-upgrades dpkg-reconfigure --priority=low unattended-upgrades
Set timezone:
dpkg-reconfigure tzdata sudo service rsyslog restart
Security:
fail2ban
sudo apt-get install fail2ban awk '{ printf "# "; print; }' /etc/fail2ban/jail.conf | sudo tee /etc/fail2ban/jail.local vim /etc/fail2ban/jail.conf
uncomment sshd section and add
enabled = true
sudo apt-get install sendmail iptables-persistent sudo service fail2ban start
Firewall ( allow established connections, traffic generated by the server itself, traffic destined for our SSH and web server ports. We will drop all other traffic):
sudo service fail2ban stop sudo iptables -A INPUT -i lo -j ACCEPT sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT sudo iptables -A INPUT -j DROP #easy way to rate-limit ssh with ufw: # technically, we could do all of the iptables stuff with ufw ufw enable ufw limit ssh
If using IPv6:
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT # (replace with your undisclosed port) ip6tables -A INPUT -p icmpv6 -j ACCEPT ip6tables -A INPUT -j REJECT ip6tables -A FORWARD -j REJECT
View iptables rules:
sudo iptables -S
Save iptables rules:
sudo dpkg-reconfigure iptables-persistent sudo service fail2ban start
Create a deploy user
sudo adduser deploy sudo adduser deploy sudo su deploy
Installing Ruby (2.4)
Prereq’s:
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev nodejs cd git clone https://github.com/rbenv/rbenv.git ~/.rbenv echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc echo 'eval "$(rbenv init -)"' >> ~/.bashrc exec $SHELL git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc exec $SHELL rbenv install 2.4.0 rbenv global 2.4.0 ruby -v gem install bundler
Nginx
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7 sudo apt-get install -y apt-transport-https ca-certificates # Add Passenger APT repository sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list' sudo apt-get update # Install Passenger & Nginx sudo apt-get install -y nginx-extras passenger
Nginx update for Ubuntu 17.10 zesty
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7 sudo apt-get install -y apt-transport-https ca-certificates # Add our APT repository sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger zesty main > /etc/apt/sources.list.d/passenger.list' sudo apt-get update # Install Passenger + Nginx module sudo apt-get install -y libnginx-mod-http-passenger libnginx-mod-http-headers-more-filter nginx
Nginx update for Ubuntu 18.04 bionic
sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bionic main > /etc/apt/sources.list.d/passenger.list' sudo apt-get update sudo apt-get install -y libnginx-mod-http-passenger
Block anything you don’t want in Nginx
vim /etc/nginx/sites-enabled/default location ~ ^/(wp-admin|wp-content|wp-login) { deny all; }
Set Cache headers
location ^~ /assets/ { gzip_static on; expires 1d; add_header Cache-Control "public"; } location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ { expires 30d; access_log off; add_header Cache-Control "public"; }
Prevent access by IP
vim /etc/nginx/sites-enabled/default server { listen 80; listen 443; server_name _put_server_ip_here; return 404; }
Start Nginx:
sudo service nginx start
Nginx config:
sudo vim /etc/nginx/nginx.conf
Uncomment Phusion config – include /etc/nginx/passenger.conf;
Update passenger config:
sudo vim /etc/nginx/passenger.conf
passenger_ruby /home/deploy/.rbenv/shims/ruby;
passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;
#passenger_ruby /usr/bin/passenger_free_ruby;
Restart Nginx
sudo service nginx restart
Test Nginx Config with gixy
We will need python-pip in order to install gixy:
apt-get install python-pip pip install --upgrade pip pip -V pip install gixy gixy
Postgres
sudo apt-get install postgresql postgresql-contrib libpq-dev
Postgres update for Ubuntu 17.10
sudo apt-get install postgresql-9.6 sudo apt-get install python-psycopg2 sudo apt-get install libpq-dev
Postgres update for Ubuntu 18.04
touch /etc/apt/sources.list.d/pgdg.list sudo sh -c 'echo deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main > /etc/apt/sources.list.d/pgdg.list' wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - sudo apt-get update apt-get install postgresql-10 #Migrate Postresql from 9.6 to 10 sudo pg_dropcluster 10 main --stop sudo systemctl stop postgresql sudo pg_upgradecluster 9.6 main sudo pg_dropcluster 9.6 main --stop
Ensure UTF-8 support before creating a new database
vim /etc/profile.d/lang.sh
Add the following if not already there:
export LANGUAGE="en_US.UTF-8" export LANG="en_US.UTF-8" export LC_ALL="en_US.UTF-8"
Create a Database with a user (Make sure to change the app name):
sudo su - postgres createuser --pwprompt deploy sudo su - deploy psql create database your_db_name_production_new with owner=deploy encoding='UTF-8'; exit
Creating dumps
pg_dump db_name_production -U deploy -h localhost > db_name_production_backup # To restore: psql -d db_name_production -f db_name_production_backup #if you want to scp it from somewhere else(assuming dumps are in ~/dumps): scp -r root@server_ip:~/dumps ~/destination_dumps
Capistrano
On your local machine add to your Gemfile:
gem 'capistrano', '~> 3.7', '>= 3.7.1' gem 'capistrano-passenger', '~> 0.2.0' gem 'capistrano-rails', '~> 1.2' group :production do gem 'capistrano-rbenv', '~> 2.1' end
Generate configs:
cap install STAGES=production
Update Capfile:
# If you are using rbenv add these lines: # require 'capistrano/rbenv' # set :rbenv_type, :user # set :rbenv_ruby, '2.4.0'
Update deploy.rb:
set :application, "my_app_name" set :repo_url, "[email protected]:me/my_repo.git" set :deploy_to, '/home/deploy/my_app_name' append :linked_files, "config/database.yml", "config/secrets.yml" append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "vendor/bundle", "public/system", "public/uploads"
Update production.rb with the server IP:
server ‘127.0.0.1’, user: ‘deploy’, roles: %w{app db web}
Had to manually create database.yml and secrets.yml for now in /home/deploy/my_app_name/shared/config and copy connection data. This should be automated later
Update /etc/nginx/sites-enabled/default
server { listen 80; listen [::]:80 ipv6only=on; server_name mydomain.com; passenger_enabled on; rails_env production; root /home/deploy/my_app_name/current/public; #action cable config: location /cable { passenger_app_group_name app_name_websocket; passenger_force_max_concurrent_requests_per_process 0; } }
Redis
sudo apt-get install redis-server
sudo service nginx restart
Time Synchronization
sudo apt-get update sudo apt-get install ntp systemctl start ntp.service sudo ntpq -p
Let’s Encrypt (optional step)
Install certbot
apt-get install software-properties-common add-apt-repository ppa:certbot/certbot apt-get update apt-get install certbot
Add to /etc/nginx/sites-available/default (this may be necessary for the Webroot plugin, which we’ll use to generate the certs)
location ^~ /.well-known/acme-challenge { root /var/www/html; default_type "text/plain"; try_files $uri =404; }
Verify config and restart:
nginx -t systemctl restart nginx
Get your webroot path from /etc/nginx/sites-available/default
Generate certs(replace domain an dweb-root-path):
certbot certonly --webroot --webroot-path=/var/www/html -d example.com -d www.example.com
Add a snippet:
vim /etc/nginx/snippets/ssl-example.com.conf
Paste the following:
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
And another one for ssl params:
vim /etc/nginx/snippets/ssl-params.conf
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; ssl_ecdh_curve secp384r1; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff;
Update nginx config (this will allow both http and https):
vim /etc/nginx/sites-available/default
server { listen 80 default_server; listen [::]:80 default_server; listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; server_name example.com www.example.com; include snippets/ssl-example.com.conf; include snippets/ssl-params.conf; ...
Automate certificate renewal:
crontab -e
15 1 * * * /usr/bin/certbot renew --quiet --renew-hook "/bin/systemctl reload nginx" 20 1 * * * nginx -s reloadv
The above will run at 1:15 am every day, then reloads nginx config at 1:20 am.
To view certs expiration dates:
apt-get install ssl-cert-check ssl-cert-check -c /etc/letsencrypt/live/example.com/cert.pem
Additional Mac Setup
brew install ssh-copy-id
ssh-copy-id deploy@IPADDRESS
This will allow you to ssh without a password the next time you ssh.
Useful logs
cat /var/log/redis/redis-server.log
sudo cat /var/log/fail2ban.log
sudo cat /var/log/nginx/error.log
fail2ban grouped by IP:
awk '($(NF-1) = /Ban/){print $NF}' /var/log/fail2ban.log | sort | uniq -c | sort -n
Useful monitoring commands
Ram usage:
free -m
top
Space:
df -ah
du -hs * | sort -h
Another way to get space info
apt-get install ncdu
ncdu /
Network activity:
netstat -tulpn
Linode ipv6 issue
If you can’t get apt-get to work, try to update /etc/gai.conf and uncomment line 54: precedence ::ffff:0:0/96 100
Linode Monitoring – Longview
Linode also provides Longview – web UI to access your server stats. The free version is limited to the last 24 hours.
Here is the official guide
I had to update iptables rules and restart the longview service:
iptables -I INPUT -s longview.linode.com -j ACCEPT iptables -I OUTPUT -d longview.linode.com -j ACCEPT sudo service longview restart
Useful links:
- Deploy ActionCable in production
- Deploy Ruby On Rails on Ubuntu 16.04 Xenial Xerus
- Capistrano Github
- Pagespeed – The tool and optimization guide
- Nifty Nginx config generator
Now, while it’s fun figuring these things out, git push heroku master sounds a lot easier.