Ubuntu 20.04 Web Server Tutorial
Tutorial for MAC/PC/LINUX

Gabriel Sandberg

The Perfect LEMP Server Ubuntu 20.04 LTS
Focal Fossa with PHP 7.4

In the videos, I created the server at DigitalOcean (100USD coupon), but I also recommend Webdock.

If you need help with WordPress or Ubuntu/NGINX issues, just contact me here (English/Swedish/French). Moving WordPress site to new web hotel/server/domain typically 3600 SEK (340 EUR/395USD).

In my YouTube Channel I show you how to install an extra secure and fast web server at your web host VPS account, from your MAC/PC/LINUX computer. I also show you how to manage your server with your iPhone or Android phone.

You can customize the terminal commands below with your domain name, username etc, to make copy pasting quick and easy.

Create SSH keypair in macOS terminal

1. Create SSH keypairs on your MAC, to login to your Ubuntu 20.04 server

For highest security and convenience, save your SSH key on an encrypted external drive like an USB stick/SD card. Never create root passwords.

We will use SSH keys directly when creating the server, and later disable root and password login.

Make sure you have an empty USB stick or SD Card inserted.

Encrypt the USB stick/SD Card on your Mac as below:

Open Disk Utility

1. Select view: "show all devices"
2. With a device (not the underlying volume) selected click on the 'Erase' button.
3. Choose 'GUID Partition Map' from the 'Scheme' combo-box.
4. Choose external drive: "Erase"
5. Choose Format: "Mac OS Extended (Case-sensitive, Journaled, Encrypted)".
Give it a name (Preferably something short as you need to reference it in command line.)
Disk Utility will ask for a password (this is asked every time we plugin the drive or restart the Mac).

Open macOS Terminal and type:

ssh-keygen -t rsa -b 4096 (2048 is more compatible, but less secure)

Instead of pressing ENTER key to accept the default location, we will now save the key on the inserted media instead.

Enter the location of your external drive:

/Volumes/USBstick/mykey_rsa

Enter a password for your key (in case you get hacked or lose it).

Private keys without password protection are very unsafe as it's easy to recreate all your private keys if your computer is stolen, hacked or sold (even if discs are formatted before).

pbcopy < /Volumes/USBstick/mykey_rsa.pub # copies your public key to the clipboard so you can paste it into the terminal later

Make a copy of your private key to put in the safe
cp /Volumes/USBstick/mykey_rsa /Volumes/My SD Card/mykey_rsa # Makes a copy if you are a bit paranoid losing your key,

If you prefer, create a new key on your second media storage instead of copy, so you can deactivate one key if it's lost.

 


Windows 10

1. Create SSH keypairs in Windows 10 to login to your Ubuntu 20.04 server

For this you can use Bitvise (free) https://www.bitvise.com/ssh-client-download
(or Puttygen http://www.chiark.greenend.org.uk/ + Filezilla https://filezilla-project.org/)

We will use Bitvise here as it has more security options than Puttygen + Filezilla and also saves you time as it has a more efficient workflow (saves your key password etc.).

Create 4096 strong RSA-SHA2 keys with password protected private key (18+ characters). Export the private key (Open SSH) directly to an encrypted external drive (e.g. an SD Card), never let it touch your OS disc (C-drive on PC).

Private keys without password protection are very unsafe as it's easy to recreate all your private keys if your computer is stolen, hacked or sold (even if discs are formatted before).

Keep Bitvise/Puttygen open until you have created your droplet at DigitalOcean and created your sudo user in the steps below. You shall also export your public key (Open SSH) to your drive.


Windows 10

2. Create an Ubuntu 20.04 VPS server at your web host

First create an account at for instance DigitalOcean (100USD coupon). Login to Digitalocean and click "Create -> Droplets -> . In the tabs menu, select "Distributions" and Ubuntu 20.04 (LTS) 64. You can also check the "Marketplace" tab if "LEMP on 20.04 " (not LAMP) is available (it installs NGINX, MySQL, PHP, Fail2ban, Certbot, UFW, on Ubuntu 20.04 so you can skip step #4 below).

As an alternative to DigitalOcean you can setup your server at Webdock who has great deals from 4USD/month on amazingly fast web servers.

At DigitalOcean, scroll down to choose size. The second cheapest option is all fine, currently 10 USD/month. It's very easy to scale up later, but trickier to scale down if you initially choose bigger than you need. You can even scale up temporarily later when you run campaigns and then scale down again. For a Wordpress site you'll probably need at least the 10-20 USD/month options.

Scroll down and choose a datacenter near most of your users.

On "select additional options" you can select "Monitoring". "Networking" opens for vulnerabilities, but can be especially useful in some cases.

Click "New SSH keys" button.

Copy your public key from your USB Flash/Bitvise/Puttygen and paste it into the Digitalocean SSH key prompt. Keep Bitvise/Puttygen open until next step.


Windows 10

3. Connect to your server

For this you can of course use your terminal or whatever you are used to, but I will show you an alternative which will save you time and even let you manage your servers via your phone/SmartTV if you just lost your laptop one day. With Termius cloud solution you can both transfer files to and from your web server and use it as a terminal. Use it on macOS, Windows 10, Linux, Android, or iOS.

Go to https://termius.com and download the versions most convenient for you. In the video I will show you how to use Termius.com on macOS Catalina and Android.

Create a Termius account and click "add host". Add the IP address you got from DigitalOcean, and your username (root). Click on "Add keys" to paste your public key, and upload your private key from your USB stick/SD Card or wherever you stored it.

Click on your server to connect to it.

You can now also download Termius on your phone from Termius on Google Play

or Termius on Apple Store

Login with your account and you will now already find your server name. Click to connect and open the terminal in your phone.

In the video I will show you how to use Termius on your Android phone on the go, and how to connect your phone remotely to TVs/monitors on your network. Also connect a full-size keyboard to your phone (via Bluetooth/OTG USB etc.) and you won't miss your laptop much.

Termius can be very handy for urgent fixes when you are far away from your laptop or you simply have lost it. Uploading private SSH keys to the cloud is of course a security risk, but so is also losing your laptop, and hard disc crashes etc.


Windows 10

3. Connect to your server with Bitvise on PC/Vine

Open Bitvise and add your private key to the Client Key Manager if you created it with Puttygen. In the video I show you how to create the keys with Bitvise.
Copy the IP of your droplet from Digitalocean to Bitvise. Select your private key and enter your private key password + IP address and username (root)
Click login and switch to the Bitvise console window (alt tab).


Windows 10

4. Install NGINX, MySQL, MariaDB, PHP 7.4

In your terminal type below commands to install NGINX, MySQL, and MariaDB, PHP 7.4 on your Ubuntu 20.04 web server:

sudo apt update

sudo apt-get install software-properties-common

sudo add-apt-repository ppa:ondrej/php -y

sudo add-apt-repository ppa:ondrej/nginx -y

sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://mirrors.up.pt/pub/mariadb/repo/10.5/ubuntu focal main'

sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc'

sudo apt install nginx

sudo systemctl start nginx.service

(or locally on WSL sudo service nginx start )

(or sudo /etc/init.d/nginx start )

sudo systemctl enable nginx.service

sudo apt install php7.4-fpm php7.4-common php7.4-mysql php7.4-xml php7.4-xmlrpc php7.4-curl php7.4-gd php7.4-imagick php7.4-cli php7.4-dev php7.4-imap php7.4-mbstring php7.4-opcache php7.4-soap php7.4-zip unzip -y

Restart your new web server with the below command:

sudo reboot

sudo apt-get update # Fetches the list of available updates
sudo apt-get upgrade # Strictly upgrades the current packages
sudo apt-get dist-upgrade # Installs updates (new ones)
sudo apt autoremove # Remove files you don't need

sudo apt install unattended-upgrades # Will automatically install updates, (make sure to do daily backups).

sudo dpkg-reconfigure unattended-upgrades # Configure automatic updates

Choose “Yes” and hit enter. Edit the configuration file:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Adjust code according to below:

// Automatically upgrade packages from these (origin:archive) pairs
//
// Note that in Ubuntu security updates may pull in new dependencies
// from non-security sources (e.g. chromium). By allowing the release
// pocket these get automatically pulled in.
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
// Extended Security Maintenance; doesn't necessarily exist for
// every release and this system may not have it installed, but if
// available, the policy for updates is such that unattended-upgrades
// should also install from here by default.
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
// "${distro_id}:${distro_codename}-updates";
// "${distro_id}:${distro_codename}-proposed";
// "${distro_id}:${distro_codename}-backports";
};
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

Now run:

sudo nano /etc/apt/apt.conf.d/20auto-upgrades

Add below line (if not there)

APT::Periodic::Unattended-Upgrade "1";

You can now go to "Networking" in the DigitalOcean Control Panel and, "add your domain" to the droplet.

Keep track of your server with https://amplify.nginx.com/ to see if you need to upgrade memory etc.

Install Amplify to track NGINX issues etc, and use it as a health tracker of your server.


Windows 10

Personalize this tutorial with your domain name etc to copy commands quickly and easily

Enter your personal data in the form fields to customize the commands below the form
(customizes text in green):













You can already test if your server is working by clicking
http://{{tutorial.yourdomain}}
http://{{tutorial.ipno}}

Edit your page with Nano SSH editor:
sudo nano /var/www/html/index.html


Windows 10

5. Add a new Ubuntu user and disable pw and root user

sudo adduser {{tutorial.sudouser}} Enter a password and hit Enter on all for default values
sudo usermod -aG sudo {{tutorial.sudouser}}
su - {{tutorial.sudouser}} (switches to the new user)
sudo ls -la /root (just to test that the new user has sudo privilege)
sudo mkdir ~/.ssh
sudo chmod 700 ~/.ssh
sudo nano ~/.ssh/authorized_keys

Now insert your public key by pasting it into the Nano editor.
pbcopy < /Volumes/USBstick/mykey_rsa.pub # copies your public key to the clipboard so you can paste it.
(or copy from Notepad/Bitvise/Puttygen)

Exit and save with CTRL X and select yes.
Run:
sudo chmod 600 ~/.ssh/authorized_keys
sudo chown {{tutorial.sudouser}} -R ~/.ssh

Disable password and root login:

sudo nano /etc/ssh/sshd_config

Search for below directives and modify to "no":
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
PermitRootLogin no
CTRL X and save

Now run:

echo 'UseRoaming no' | sudo tee -a /etc/ssh/ssh_config

sudo systemctl reload sshd

Test if SSH still is running
sudo service ssh status (ctrl c to exit, twice)

If not running, run:
sudo service ssh start

Test login
ssh {{tutorial.sudouser}}@{{tutorial.ipno}}

You can reboot your Ubuntu server with:

sudo reboot

(This will log you out, so you can wait until you are done also)


Windows 10

6. AppArmor Profiles

AppArmor is a Linux Security Module implementation of name-based mandatory access controls. AppArmor confines individual programs to a set of listed files and posix 1003.1e draft capabilities

AppArmor is installed and loaded by default. It uses profiles of an application to determine what files and permissions the application requires. Some packages will install their own profiles, and additional profiles can be found in the apparmor-profiles package. To install the apparmor-profiles package, run:

sudo apt install apparmor-profiles
sudo apparmor_status


Windows 10

7. Harden network with sysctl settings

sudo nano /etc/sysctl.conf

Un-comment or add the following lines:

# IP Spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Block SYN attacks
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
# Log Martians
net.ipv4.conf.all.log_martians = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
# Ignore Directed pings
net.ipv4.icmp_echo_ignore_all = 1

To reload sysctl with the latest changes, enter:

sudo sysctl -p



Windows 10

8. Secure shared memory

sudo nano /etc/fstab

Add the following line and save:

tmpfs /run/shm tmpfs defaults,noexec,nosuid 0 0



Windows 10

9. Configure PHP 7.4

sudo nano /etc/php/7.4/fpm/php.ini

cgi.fix_pathinfo=0
file_uploads = On
allow_url_fopen = On
memory_limit = 256M
upload_max_filesize = 128M
max_execution_time = 360
post_max_size = 128M

Add "phpinfo" to: disable_functions = phpinfo,..

When done with changes, hold ctrl and click x, save, enter. Restart PHP with the below command:

sudo systemctl restart php7.4-fpm



Windows 10

10. Install MariaDB Secure MySQL Create Databases

Copy your mysql root password (If any. If empty, then skip and exit):

sudo nano /root/.digitalocean_password

Paste it in for instance Notepad

MariaDB probably has a better future than MySQL, as Oracle bought MySQL and the original MySQL team now develops MariaDB. MariaDB also has more features and speed improvements over MySQL.

sudo apt-get update
sudo apt-get install mariadb-server -y
sudo systemctl status mariadb (ctrl c to quit)

Run: sudo mysql_secure_installation (answer ENTER, NO, NO on questions about password and then YES to all)

Start MariaDB:

sudo service mysql start
Check version: sudo mysql -v

To login run:

sudo mysql -u root -p

(You might first need to enter your Ubuntu sudo user password. On next password request, just hit enter or use your mysql root password)

Use below commands to create your first database:

CREATE DATABASE {{tutorial.dbname}} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER '{{tutorial.dbuser}}'@'localhost' IDENTIFIED BY '{{tutorial.dbpw}}';

GRANT ALL PRIVILEGES ON {{tutorial.dbname}}.* TO '{{tutorial.dbuser}}'@'localhost' IDENTIFIED BY '{{tutorial.dbpw}}';

Exit with quit or ctrl c

(Character set utf8mb4 is newer than utf8 and can also store emojis etc, now default though.)

Instead of PHP MyAdmin you can use Adminer to admin your database, which is both easier and much safer as you delete the file when done.

To make a copy of your database:
mysqldump -u username -p prereleasedb > dp-release1.sql
Import mysql -u username -p release1db <dp-release1.sql



Windows 10

11. Configure Fail2ban for SSH and NGINX

Install Fail2ban

sudo apt install fail2ban

sudo systemctl status fail2ban

Copy before edit

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jailORIG.local

sudo nano /etc/fail2ban/jail.conf

Add what's missing below (search on "[nginx-http-auth]"):

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log

Exit with CRTL X and save

Restart Fail2ban

sudo systemctl restart fail2ban
or sudo service fail2ban restart
sudo fail2ban-client status
Check status sudo iptables -S (to view changed ip tables)
sudo fail2ban-client status nginx-http-auth Too see bans



Windows 10

12. Configure UFW Firewall

sudo ufw status Shall be inactive

sudo ufw default deny incoming

sudo ufw default allow outgoing

sudo ufw allow ssh

sudo ufw delete allow 80/tcp

sudo ufw allow 'Nginx Full' (or

sudo ufw allow 'Nginx HTTP')

sudo ufw allow 'Nginx HTTPS'

sudo ufw enable

sudo ufw status

sudo ufw status verbose

Other useful commands:

sudo ufw disable

sudo ufw reset



Windows 10

13. Remove services not used

List services: netstat -lp

Remove a service: sudo apt-get remove telnetd

Remove several services: sudo apt-get remove isc-dhcp-client ftp info eject



Windows 10

14. Setup NGINX for several sites and test sub domains with NGINX server blocks

Check so NGINX is allowed in the firewall
sudo ufw status

If not, allow NGINX with below command
sudo ufw allow 'Nginx HTTP'

Check so NGINX is installed correctly and active
systemctl status nginx

To start the NGINX server when it is stopped, type:
sudo systemctl start nginx

To stop your NGINX server, type:
sudo systemctl stop nginx

To stop and then start the service again, type:
sudo systemctl restart nginx

If you are only making configuration changes, Nginx can often reload without dropping connections. To do this, type:
sudo systemctl reload nginx

Nginx on Ubuntu 20.04 has one server block enabled by default that is configured to serve documents out of a directory at /var/www/html.

While this works well for a single site, it can become unwieldy if you are hosting multiple sites. Instead of modifying /var/www/html,

let’s create a directory structure within /var/www for our {{tutorial.yourdomain}} site,

leaving /var/www/html in place as the default directory to be served if a client request doesn’t match any other sites.

Create content directories for your web sites {{tutorial.yourdomain}} and {{tutorial.your2nddomain}}, and also for your test sites test.{{tutorial.yourdomain}} and test.{{tutorial.your2nddomain}}, so 4 directories:

sudo mkdir -p /var/www/{{tutorial.yourdomain}}/html
sudo mkdir -p /var/www/{{tutorial.yourdomain}}/test
sudo mkdir -p /var/www/{{tutorial.your2nddomain}}/html
sudo mkdir -p /var/www/{{tutorial.your2nddomain}}/test

Assign ownership of the directories with the $USER environment variable:

sudo chmod -R 755 /var/www/{{tutorial.yourdomain}}
sudo chmod -R 755 /var/www/{{tutorial.your2nddomain}}

Add some code:

sudo nano /var/www/{{tutorial.yourdomain}}/html/index.html

Add some sample HTML

Change ownership from root to your new sudouser

sudo chown -R {{tutorial.sudouser}} /var/www/

You can also create new users for the second domain, and even for the test domains if you want to limit access to certain team members or customers.

If we want any regular non-root user to be able to modify files in these directories, we can change the ownership to "whoami".

sudo chown -R $(whoami):$(whoami) /var/www/{{tutorial.yourdomain}}/html

sudo chown -R $(whoami):$(whoami) /var/www/{{tutorial.your2nddomain}}/html

The $(whoami) variable will take the value of the user you are currently logged in as.

Create a blank NGINX config file

sudo nano /etc/nginx/sites-available/{{tutorial.yourdomain}}

to paste below code into:

# server_name {{tutorial.yourdomain}} www.{{tutorial.yourdomain}} test.{{tutorial.yourdomain}}; server { listen 80; server_name www.{{tutorial.yourdomain}}; # www to non-www for SEO canonical reasons return 301 http://{{tutorial.yourdomain}}$request_uri; } server { listen 80; server_name {{tutorial.yourdomain}}; root /var/www/{{tutorial.yourdomain}}/html; index index.html index.htm index.php index.nginx-debian.html; location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; } location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|webp)$ { expires 24h; # add_header Cache-Control private; add_header Cache-Control no-store; } # For WordPress permalinks location / { try_files $uri $uri/ /index.php$is_args$args; } } # All below for test subdomain server { listen 80; server_name test.{{tutorial.yourdomain}}; root /var/www/{{tutorial.yourdomain}}/html; index index.html index.htm index.php index.nginx-debian.html; location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; } location = /favicon.ico { log_not_found off; access_log off; } location = /robots.txt { log_not_found off; access_log off; allow all; } location ~* \.(ico|css|js|gif|jpeg|jpg|png|woff|ttf|otf|svg|woff2|webp)$ { expires 24h; # add_header Cache-Control private; add_header Cache-Control no-store; } # For WordPress permalinks location / { try_files $uri $uri/ /index.php$is_args$args; } }

Use CTRL C to close and save.

sudo nginx -t (check for errors)
sudo systemctl reload nginx

Now that we have our first nginx server block configuration, we can use that as a basis for our second file. Copy it over to create a new file:

sudo cp /etc/nginx/sites-available/{{tutorial.yourdomain}} /etc/nginx/sites-available/{{tutorial.your2nddomain}}

Adjust the root directive to point to our second domain's document root and adjust the server_name to match your second site's domain name:

sudo nano /etc/nginx/sites-available/{{tutorial.your2nddomain}}

Now that we have our server block files, we need to enable them. We can do this by creating symbolic links from these files to the sites-enabled directory, which Nginx reads from during startup.

We can create these links by typing:

sudo ln -s /etc/nginx/sites-available/{{tutorial.yourdomain}} /etc/nginx/sites-enabled/

To avoid a possible hash bucket memory problem that can arise from adding additional server names, we will go ahead and adjust a single value within our /etc/nginx/nginx.conf file. Open the file now with:

sudo nano /etc/nginx/nginx.conf

Uncomment below line and change to:

server_names_hash_bucket_size 128;

Also add below lines:

client_max_body_size 128M; server_tokens off; (hides Nginx server version)

You can also uncomment all lines in the Gzip paragraph.
Save and close the file when you are finished.
Next, test to make sure that there are no syntax errors in any of our Nginx files:

sudo nginx -t

If no problems were found, restart Nginx to enable our changes:

sudo systemctl restart nginx

You can already test if your web sites are working by clicking
http://{{tutorial.yourdomain}}, http://{{tutorial.your2nddomain}},
http://test.{{tutorial.yourdomain}}, and http://test.{{tutorial.your2nddomain}}

You can edit your pages with Nano SSH editor

sudo nano /var/www/{{tutorial.yourdomain}}/html/index.html

sudo nano /var/www/{{tutorial.your2nddomain}}/html/index.html

sudo nano /var/www/{{tutorial.yourdomain}}/test/index.html

sudo nano /var/www/{{tutorial.your2nddomain}}/test/index.html


Improve your NGINX Server's Performance and Security by following below tutorial after setting up SLL/https.



Windows 10

15. Edit Hosts file in macOS, Windows 10, Linux (optional)

If you do not have any domains registered and instead just want to load http://{{tutorial.yourdomain}} and http://{{tutorial.your2nddomain}} as a test, you can edit the hosts file in your computer to point these domains to your server.

To edit hosts file in Linux or Mac, run sudo nano /etc/hosts. In Windows, follow this guide to edit hosts.

Or open Notepad as Administrator and open C:\Windows\System32\drivers\etc\hosts

Once the hosts file is open, enter a new line:

{{tutorial.ipno}} {{tutorial.yourdomain}} x.x.x.x {{tutorial.your2nddomain}}

Replace x.x.x.x with your 2nd web server’s IP.

If you don’t know your web server’s IP, you can find out with:

ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'



Windows 10

17. Configuring SSL certificate, enabling encrypted HTTP: HTTPS

If you didn't use DigitalOcean image install in step 2 above,
you will need to install certbot manually:

sudo ufw allow https
sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository universe
sudo apt-get update
sudo apt-get install certbot python3-certbot-nginx

And now install the SSL certificates (automatically edits your NGINX conf)
sudo certbot --nginx

test if automatic renewal works by

sudo certbot renew --dry-run

If you do it manually with,

sudo certbot certonly --nginx

then you will also need to edit your NGINX manually.

Test so your SSL works:

https://www.ssllabs.com/ssltest/

After you set up HTTPS, you can optionally deny HTTP traffic on port 80:

You can also change this line: listen 443 ssl; to:

listen 443 ssl http2;

DONE! :-)

You can check that the files exist by running this command:

sudo ls -l /etc/letsencrypt/live/{{tutorial.yourdomain}}

Harden NGINX with Diffie-Hellman parameter for DHE ciphersuites
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048



Windows 10

18. Improve your NGINX Server's Performance and Security

Prevent clickjacking attacks, downgrade attacks, cross-site scripting attacks, and other code injection attacks.

Edit your general NGINX config, run:

sudo nano /etc/nginx/nginx.conf

Add below code under SLL settings:

##
# Security headers
##
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
##

##
# Buffer policy
##
client_body_buffer_size 1K;
client_header_buffer_size 1k;
large_client_header_buffers 2 1k;
##

Speed optimize your NGINX config

Add below directives in your NGINX config and you are all set for SSL optimisation.

# SSL Settings
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GC$;

# Optimize session cache
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;

# Enable session tickets
ssl_session_tickets on;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;


HTTP/2 has lot of benefits over HTTP, like allowing the browser to download files in parallel, and allowing the server to push resources, among others. All you have to do is to replace http with http2 in your default server block. That’s it, and you get lots and lots of benefits.

Edit your site specific NGINX config, run:

sudo nano /etc/nginx/sites-available/{{tutorial.yourdomain}}

change this line: listen 443 ssl; to:
listen 443 ssl http2;

Client-side Caching

Change this line:

location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ { expires max; }

to:

location ~* \.(ico|gif|jpeg|jpg|webp|png|svg|eot|otf|woff|woff2|ttf|ogg)$ {
expires 30d;
}
location ~* \.(css|js)$ {
expires 7d;
}

Microcaching

If you haven’t heard about this until now, then you are in luck today! Microcaching is a caching technique in which content is cached for a very short period of time, perhaps as little as 1 second. This effectively means that updates to the site are delayed by no more than a second, which in many cases is perfectly acceptable. This is particularly useful for API responses which are the same for all users. Use these directives to set microcaching with the path at /tmp/cacheapi with 100MB cache with a max size of 1GB of cache folder that updates cache in the background.

proxy_cache_path /tmp/cacheapi{{tutorial.yourdomain}} levels=1:2 keys_zone=microcacheapi{{tutorial.yourdomain}}:100m max_size=1g inactive=1d use_temp_path=off;
...
server {

...
location /api/ {
# Micro caching
proxy_cache microcacheapi;
proxy_cache_valid 200 1s;
proxy_cache_use_stale updating;
proxy_cache_background_update on;
proxy_cache_lock on;
...
...

}
...
}

And add below to your

sudo nano /etc/nginx/nginx.conf
http {

...
add_header X-Cache-Status $upstream_cache_status;
...

}

Also uncomment your Gzip settings:

##
# Gzip Settings
##

gzip on;

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

##



Windows 10

19. Install and Secure WordPress

cd /tmp
wget https://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz
cp /tmp/wordpress/wp-config-sample.php /tmp/wordpress/wp-config.php
mkdir /tmp/wordpress/wp-content/upgrade
sudo cp -a /tmp/wordpress/. /var/www/{{tutorial.yourdomain}}/html

Run: curl -s https://api.wordpress.org/secret-key/1.1/salt/

Copy the result and replace dummy in
sudo nano /var/www/{{tutorial.yourdomain}}/html/wp-config.php

Add DB credentials and details
define('FS_METHOD', 'direct');
(Makes it possible to autoupdate WordPress, but you can comment this line for a more secure WordPress)
/** Database Charset to use in creating database tables. */
define('DB_CHARSET', 'utf8mb4');
/** The Database Collate type. Don't change this if in doubt. */
define('DB_COLLATE', 'utf8mb4_unicode_ci');

You can also forse connection via UNIX Socket instead of TCPIP for added performance and security
Find the path to your MySQL Unix socket:
netstat -ln | grep "unix.*mysql"
Change:
define('DB_HOST', 'localhost');
To:
define('DB_HOST', 'localhost:/var/lib/mysql/mysql.sock');
add this line to my configuration sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf:
skip-networking=ON;
(normally not neccesary for newer than UBUNTU 16.04 though)

sudo service mysql restart


https://codex.wordpress.org/Nginx

To enable WordPress auto update and admin at the same time as SFTP uploads:

sudo adduser {{tutorial.sudouser}} www-data (adds your user to NGINX user group)
sudo chown -R www-data /var/www/{{tutorial.yourdomain}}/html/
sudo usermod -a -G www-data {{tutorial.sudouser}}
sudo chgrp www-data /var/www/{{tutorial.yourdomain}}/html/
sudo chmod g+rwxs /var/www/{{tutorial.yourdomain}}/html/
sudo chmod -R 775 /var/www/{{tutorial.yourdomain}}/html/

Browse to your domain and follow the WordPress installation

WordPress should be installed and ready to use! Some common next steps are to choose the permalinks setting for your posts (can be found in Settings > Permalinks) or to select a new theme (in Appearance > Themes). If this is your first time using WordPress, explore the interface a bit to get acquainted with your new CMS.

Install the following WordPress security/performance plugins:
UpdraftPlus - Backup/Restore, Wordfence Security, Disable REST API, WP-DBManager
Captcha by BestWebSoft (uncheck login and check registration + password reset under WordPress tab)
WP Fastest Cache

Make sure to setup messaging from your WordPress site so you can receive password reset, contact form messages etc.

Setup WP Mail SMTP



Windows 10

20. Install NodeJS with NVM

curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
source ~/.profile
nvm --version
nvm list-remote
nvm install node

Run: nvm install v14.15.4

to for example install Node.js version v14.15.4
nvm current
node --version
nvm use v15.8.0
nvm uninstall v15.8.0

Install PM2 to make apps run in the background as a service: sudo npm install pm2@latest -g

Start app and add it to PM2’s process list
pm2 start hello.js
pm2 startup systemd
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u {{tutorial.sudouser}} --hp /home/{{tutorial.sudouser}}
pm2 save
sudo reboot Optional
systemctl status pm2-{{tutorial.sudouser}}
pm2 stop app_name_or_id
pm2 restart app_name_or_id

Add below code in your nginx domain config do direct traffic to your node app instance:

location /app2 {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}


Windows 10

Congratulations, you're done! :-)

This tutorial was really for the most paranoids, but rest assure you have one of the safest web servers in the World now!

To store private keys and passwords, use for instance OneDrive Vault
or Bitdefender, or encrypted external drives.

When connecting to your server, try to use wired Ethernet connection instead of Wifi for added security.

Also good to delete your MySQL/terminal command history with below command:

rm -rf ~/.mysql_history
sudo rm -rf /root/.mysql_history
history -cw (Keep the 1st space)
clear

You can also check the error logs now, to see that all is OK:

sudo nano /var/log/nginx/error.log
sudo nano /var/log/nginx/access.log


Thank you for following the whole course!!

If you need help/support with WordPress or Ubuntu/NGINX issues, just contact me here (English/Swedish/French). Moving WordPress site to new web hotel/server/domain typically 3600 SEK.

Or check out my YouTube Channel


10 Euro credit at Webdock

Use coupon code gabriel.nu when signing up for Webdock and get a 10 Euro credit applied to your account. Coupon can only be used once per account and is only valid for new signups. You must add a credit card to your account before activating the coupon. The coupon is valid till January 1st 2021.

Webdock is a developer friendly VPS hosting environment supercharged with Linux Containers. Perfectly configured web-servers. High limits. Blazing-fast SSD disks. Awesome control panel.

100USD coupon for your VPS

Free webhosting at DigitalOcean for your Ubuntu 20.04 web server.

DigitalOcean has 12 datacenters all over the world, and an easy to use control panel.

Need support with WordPress or Ubuntu?

Just contact me here
(English/Swedish/French).

Or at my LinkedIn

To move a WordPress site to a new server or domain I normally charge 3600 SEK
(340 EUR/395USD).

Copyright © Gabriel Sandberg