DigitialOcean Nginx Website Setup ( Virtual Hosts )
These are the configuration files for an Ubuntu 20.04 Droplet on Ubuntu. The end results is a custom Nginx Server with HTTP2 & SSL Certificates for each website on a droplet.
These are the configuration files for an Ubuntu 20.04 Droplet on Ubuntu. The end results is a custom Nginx Server with HTTP2 & SSL Certificates for each website on a droplet.
My setup for creating a new Droplet is specific for my needs, which is usually just nginx with SSL & HTTP2. I am also usually running more than one website on a droplet, so I typically setup nginx to use Virtual Hosts. I also like using Zsh, and specifically, Oh My Zsh.
Follow DitigalOceans suggested Initial Server Setup with ubuntu 16.04 before proceeding.
Once you have created your new Droplet, I am assuming you have your new Droplet's IP Address, and that you have also added your SSH keys so you can access remotely.
OK, now that you have a barebones Droplet, we can get started setting it up. Here is how I setup mine:
In a new terminal window type the following:
myusername
below to whatever username you created as a part of the Initial Server Setup with ubuntu 16.04](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-16-04)123.45.67.890
below to your droplets IP addressssh myusername@123.45.67.890
sudo apt-get update
sudo apt-get install nginx -y
sudo apt-get install git -y
sudo apt-get install zsh -y
sudo apt-get install unzip -y
sudo apt-get install --reinstall build-essential -y
At this point you may wish to also check out the Security / Firewall discussions on DigitalOceans How To Install Nginx on Ubuntu 16.04.
I personally do not like seeing a message every time I login, so you can hush that login message via:
touch ~/.hushlogin
Next I install Zsh:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
Then I add a really sweet zsh-syntax-highlighting plugin that I LOVE:
cd ~
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git
echo "source ${(q-)PWD}/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" >> ${ZDOTDIR:-$HOME}/.zshrc
source ./zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
Then I add a really sweet zsh-autosuggestions plugin that I LOVE:
git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions
Finally, I edit the ~/.zshrc
file ( after making a backup file at ~/.zshrc.backup
):
cp ~/.zshrc ~/.zshrc.backup
cat /dev/null > ~/.zshrc
nano ~/.zshrc
and replace the entire contents of the file with:
export ZSH=$HOME/.oh-my-zsh
ZSH_THEME="robbyrussell"
CASE_SENSITIVE="true"
HYPHEN_INSENSITIVE="true"
DISABLE_AUTO_TITLE="true"
ENABLE_CORRECTION="true"
COMPLETION_WAITING_DOTS="true"
DISABLE_UNTRACKED_FILES_DIRTY="true"
case $TERM in
xterm*)
precmd () {print -Pn "\e]0;Digital Ocean Droplet\a"}
;;
esac
plugins=(git zsh-autosuggestions)
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
source $ZSH/oh-my-zsh.sh
source $HOME/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
Next, if you want to make nano
a little prettier to use, you can do the following:
curl https://raw.githubusercontent.com/scopatz/nanorc/master/install.sh | sh
I know I am going to be using SSL on this new Droplet, so I go ahead and get that ready:
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
sudo apt install certbot python3-certbot-nginx
Then you can go ahead and setup CRON jobs to automagically update any SSL certs you end up installing:
sudo crontab -e
Then add the following to your CRON tab:
30 2 * * 1 /opt/letsencrypt/letsencrypt-auto renew >> /var/log/le-renew.log
35 2 * * 1 /bin/systemctl reload nginx
Now, whenever I am ready to setup a new website on this Droplet, I can just hop on over to my other documentation for this :)
DigitialOcean Nginx Website Setup ( Virtual Hosts )
So what if you want to install some other stuff, like PHP or MySQL? I keep track of those installs steps below, but don't always need them:
There is a really good writeup on getting both PHP & MySQL setup on DigitalOceans How To Install Linux, Nginx, MySQL, PHP (LEMP stack) in Ubuntu 16.04 document. But some of the stuff is just running basic commands in terminal:
Run the following:
sudo apt-get install mysql-server -y
sudo mysql_secure_installation
Then follow the on screen prompt. You will be asked to create a new password, so you might want to create that ahead of time.
Now, its probably a good idea to not just have the root user on there, so let's create a custom user we'll use for MySQL by running the following in terminal:
mysql -u root -p
When prompted, enter the password you just setup when installing MySQL.
You will probably not have a database yet, so let's go ahead and create one:
mydatabase
to whatever you want to call your databasenewuser
to whatever you want your new username to bepassword
to whatever you want your new password to beCREATE DATABASE mydatabase;
Now, let's create our new user and give them access to this database ( and ONLY this database ):
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON mydatabase.* TO 'newuser'@'localhost';
FLUSH PRIVILEGES;
exit;
Cool, now that you have MySQL setup, maybe you want to use PHP to connect to it. Or maybe you just want PHP and don't need MySQL right now, here is how to get that setup:
sudo apt-get install php-fpm php-mysql -y
Now you will need to tweak one setting in your php.ini
file to get things running smoothly:
sudo nano /etc/php/7.0/fpm/php.ini
Look for the a commented out line of ;cgi.fix_pathinfo=1
, uncomment it, and change it to cgi.fix_pathinfo=0
. Save, then exit :)
Now you need to restart PHP so the changes take affect:
sudo systemctl restart php7.0-fpm
I recently discovered that running npm install
on larger projects seemed to be randomly failing at different points throughout the install process. After a bit of research, I found the issue was memory related, and creating a Swap Drive could be a temp solution. It is important to note that Swap Files on SSD drives are not recommended. DigitalOcean uses SSD. However, for my case it did not make sense for me to upgrade the entire server for a memory issue because of a single command I needed to run one time. So, if you are only going to run a swap file for a minute or so, here are the steps you need to create a Temp Swap Drive:
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
When you are done, run the following:
sudo swapoff /swapfile
The swapfile will automatically be disabled on restart.
Change all instances of website.com
to your actual website's domain.
sudo mkdir -p /var/www/website.com/html
sudo nano /etc/nginx/sites-available/website.com
Then paste in the contents of website.com
template file.
sudo ln -s /etc/nginx/sites-available/website.com /etc/nginx/sites-enabled/
NOTE: This won't work until DNS points to this newly created server.
cd /opt/letsencrypt
./letsencrypt-auto certonly -a webroot --webroot-path=/var/www/website.com/html -d website.com -d www.website.com
sudo nano /etc/nginx/snippets/ssl-website.com.conf
Paste in the following
ssl_certificate /etc/letsencrypt/live/website.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/website.com/privkey.pem;
Do this after uploading all your content.
sudo chmod -R u+rwX,go+rX,go-w /var/www/website.com/html
sudo chown -R $USER:$USER /var/www/website.com/html
Now that you have all your files setup, you can quickly test your nginx configuration by running the followin:
sudo nginx -t
You should see something like this, telling you everything is OK:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If something was wrong, it will tell you which file has an issue, and you should update that file and restest.
Once everything is verified to be correct, you can run the following to apply your new changes:
sudo systemctl restart nginx
# /etc/nginx/snippets/basic.conf | |
#Specify a charset | |
charset utf-8; | |
# Setup Content Encoding | |
gzip on; | |
gzip_min_length 1100; | |
gzip_buffers 4 32k; | |
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; | |
gzip_vary on; | |
# Force the latest IE version | |
add_header "X-UA-Compatible" "IE=Edge"; | |
# Expire rules for static content | |
# cache.appcache, your document html and data | |
location ~* \.(?:manifest|appcache|html?|xml|json)$ { | |
expires -1; | |
} | |
# Feed | |
location ~* \.(?:rss|atom)$ { | |
expires 1h; | |
} | |
# Media: images, icons, video, audio, HTC | |
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ { | |
expires 1M; | |
access_log off; | |
add_header Cache-Control "public"; | |
} | |
# CSS and Javascript | |
location ~* \.(?:css|js)$ { | |
expires 1y; | |
access_log off; | |
} | |
# Cross domain webfont access | |
location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ { | |
# Cross domain AJAX requests | |
# http://www.w3.org/TR/cors/#access-control-allow-origin-response-header | |
# **Security Warning** | |
# Do not use this without understanding the consequences. | |
# This will permit access from any other website. | |
# | |
add_header "Access-Control-Allow-Origin" "*"; | |
# Instead of using this file, consider using a specific rule such as: | |
# | |
# Allow access based on [sub]domain: | |
# add_header "Access-Control-Allow-Origin" "subdomain.example.com"; | |
expires 1M; | |
access_log off; | |
add_header Cache-Control "public"; | |
} | |
# Prevent clients from accessing hidden files (starting with a dot) | |
# This is particularly important if you store .htpasswd files in the site hierarchy | |
# Access to `/.well-known/` is allowed. | |
# https://www.mnot.net/blog/2010/04/07/well-known | |
# https://tools.ietf.org/html/rfc5785 | |
location ~* /\.(?!well-known\/) { | |
deny all; | |
} | |
# Prevent clients from accessing to backup/config/source files | |
location ~* (?:\.(?:bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$ { | |
deny all; | |
} | |
location ~ \.(?:css|htc|js|js2|js3|js4)$ { | |
gzip_vary on; | |
} | |
# PHP Support | |
location ~ \.php$ { | |
include snippets/fastcgi-php.conf; | |
fastcgi_pass unix:/run/php/php7.0-fpm.sock; | |
} | |
location ~ /\.ht { | |
deny all; | |
} |
# /etc/nginx/snippets/error-pages.conf | |
error_page 403 /error_403.html; | |
error_page 404 /error_404.html; | |
error_page 500 /error_500.html; | |
error_page 503 /error_503.html; | |
error_page 504 /error_504.html; | |
location = /error_403.html { | |
root /usr/share/nginx/html/error-pages; | |
internal; | |
} | |
location = /error_404.html { | |
root /usr/share/nginx/html/error-pages; | |
internal; | |
} | |
location = /error_500.html { | |
root /usr/share/nginx/html/error-pages; | |
internal; | |
} | |
location = /error_503.html { | |
root /usr/share/nginx/html/error-pages; | |
internal; | |
} | |
location = /error_504.html { | |
root /usr/share/nginx/html/error-pages; | |
internal; | |
} |
# /etc/nginx/nginx.conf | |
user www-data; | |
worker_processes auto; | |
pid /run/nginx.pid; | |
events { | |
worker_connections 768; | |
use epoll; | |
multi_accept on; | |
} | |
http { | |
# Basic Settings | |
sendfile on; | |
tcp_nopush on; | |
tcp_nodelay on; | |
keepalive_timeout 65; | |
types_hash_max_size 2048; | |
server_tokens off; | |
server_names_hash_bucket_size 64; | |
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains: always;"; | |
include /etc/nginx/mime.types; | |
default_type application/octet-stream; | |
# SSL Settings | |
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE | |
ssl_prefer_server_ciphers on; | |
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; | |
# Logging Settings | |
access_log /var/log/nginx/access.log; | |
error_log /var/log/nginx/error.log; | |
# Gzip Settings | |
gzip on; | |
gzip_disable "msie6"; | |
# Virtual Host Configs | |
include /etc/nginx/conf.d/*.conf; | |
include /etc/nginx/sites-enabled/*; | |
} |
# /etc/nginx/snippets/ssl-params.conf | |
# from https://cipherli.st/ | |
# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html | |
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_timeout 1h; | |
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; | |
# Disable preloading HSTS for now. You can use the commented out header line that includes | |
# the "preload" directive if you understand the implications. | |
#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; | |
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; | |
add_header X-Frame-Options DENY; | |
add_header X-Content-Type-Options nosniff; | |
ssl_dhparam /etc/ssl/certs/dhparam.pem; |
# /etc/nginx/snippets/ssl-website.com.conf | |
ssl_certificate /etc/letsencrypt/live/website.com/fullchain.pem; | |
ssl_certificate_key /etc/letsencrypt/live/website.com/privkey.pem; |
# /etc/nginx/sites-available/website.com | |
# IMPORTANT: | |
# | |
# You cannot use this until you have added SSL certificates to your server | |
# until then, you should use the template in `website.com` | |
server { | |
listen 80; | |
listen [::]:80; | |
server_name website.com www.website.com; | |
return 301 https://$server_name$request_uri; | |
} | |
server { | |
listen 443 ssl http2; | |
listen [::]:443 ssl http2; | |
include snippets/ssl-website.com.conf; | |
include snippets/ssl-params.conf; | |
include snippets/basic.conf; | |
include snippets/error-pages.conf; | |
root /var/www/website.com/html; | |
index index.php index.html index.htm index.nginx-debian.html; | |
server_name website.com www.website.com; | |
location / { | |
try_files $uri $uri/ =404; | |
} | |
} |
# /etc/nginx/sites-available/website.com | |
# IMPORTANT: | |
# | |
# You should use this for a new website untl you are able to access it | |
# using a browser. Before you can setup SSL on a website, it will need | |
# to be accessible online at both http://website.com & http://www.website.com | |
# Until both of these URL's work, you cannot create an SSL certificate. | |
# | |
# Once your SSL certificates are installed, you will need to switch this config | |
# file for the one in `website-ssl.com` so you are using HTTPS with SSL certs. | |
server { | |
listen 80; | |
listen [::]:80; | |
include snippets/basic.conf; | |
include snippets/error-pages.conf; | |
root /var/www/website.com/html; | |
index index.php index.html index.htm index.nginx-debian.html; | |
server_name website.com www.website.com; | |
location / { | |
try_files $uri $uri/ =404; | |
} | |
} |
For the error-pages.conf
file located in /etc/nginx/snippets/error-pages.conf
I use my custom error pages using Disaster Girl ... because she's awesome. Instructions for using these are on the page.
https://github.com/manifestinteractive/disaster-girl-error-pages
ssl-params.conf
file assumes you have created the/etc/ssl/certs/dhparam.pem
file by running the following command in terminal:See https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04 for more details