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:
Now, while it’s fun figuring these things out, git push heroku master sounds a lot easier.