Categories
Tech

Build a website

Do you want to run a website like this? This website is built with WordPress, hosted on a virtual private server (VPS) with nginx as a reverse proxy and MySQL as database management system (DBMS). A DNS record associates the domain (davte.it) to the public ip address of the VPS. A trusted certificate is provided by Let’s Encrypt.

You may purchase a WordPress hosting plan: your provider will take care of registering a DNS record, installing a reverse proxy (typically Apache) and a DBMS, and downloading and configuring WordPress; you will just have to run the WordPress installer to create an administrator account, then you can focus on the content.

This solution is a generally a little more expensive and easier to manage but gives you much less control on what you can do with your website. With a dedicated VPS, in fact, you may install any kind of online service, e.g. NextCloud or Jupyter or GoGS etc., and register a subdomain for each service. Furthermore, you may run your own script on this remote machine, for example a davtelepot Telegram bot.

Setup your VPS

When you buy a VPS, your provider will give you a static IPv4 address and a range of IPv6 addresses. Check that at least one static IP address is included in your VPS plan, since this is required to run a public website.

I suggest choosing Ubuntu as operating system; if available, you may select a pre-installed LEMP environment (if not available, you will have to install nginx, MySQL and php). Your provider will ask you for a root password, or generate one for you. You may log in via SSH, if your VPS comes with SSH installed and working; otherwise you will need to set it up using a remote terminal (follow your provider’s instructions).

sudo apt install openssh-server
sudo ufw allow ssh  # Firewall settings: allow incoming connections on port 22

Let’s say your IP address is 1.1.1.1. You may connect to your VPS with this command:

ssh root@1.1.1.1

Install required programs

A LEMP stack includes the reverse proxy nginx, the DMBS MySQL and the php interpreter. If your plan does not include a LEMP stack, you can install these programs individually.

sudo apt install nginx
sudo apt install mysql-server
sudo apt install php-fpm

To configure MySQL server follow these instructions.

sudo mysql_secure_installation utility

To use Let’s Encrypt as free certificate provider, install certbot for nginx.

add-apt-repository ppa:certbot/certbot
apt-get install python-certbot-nginx

Prepare WordPress

Download WordPress source code, set user and permissions to files and folders, copy the configuration sample.

cd /var/www
wget https://wordpress.org/latest.zip
unzip latest.zip
chown -R www-data:www-data wordpress  # Use nginx user or ftp user
find wordpress -type f -print0|xargs -0 chmod 664
find wordpress -type d -print0|xargs -0 chmod 775
cp wordpress/wp-config-sample.php wordpress/wp-config.php
chmod 660 wordpress/wp-config.php

Then you have to create a MySQL user and database and grant all privileges on the database to the user.

mysql
CREATE DATABASE wp-example;
CREATE USER 'aaa'@'localhost' IDENTIFIED BY 'password123';
GRANT ALL PRIVILEGES ON wp-example.* TO "aaa"@"localhost";
FLUSH PRIVILEGES;
EXIT

Once that is done, complete WordPress configuration by copying user, host, password, database name and a table prefix of your choice in wp-config.php.

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wp-example' );

/** MySQL database username */
define( 'DB_USER', 'aaa' );

/** MySQL database password */
define( 'DB_PASSWORD', 'password123' );

/** MySQL hostname */
define( 'DB_HOST', 'localhost' );

/** Database Charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8' );

/** The Database Collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', 'utf8_unicode_ci' );

/**#@+
 * Authentication Unique Keys and Salts.
 * You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
 * Paste the keys and salts here
*/
$table_prefix = 'wpe_';

Reverse proxy configuration

Assume your website is www.example.com. You want to redirect requests made to example.com to www.example.com, and allow only HTTPS requests.

HTTP requests are made on port 80 by default, whereas HTTPS requests on port 443. The former consist of plain text, the latter are encrypted with server’s certificate.

For now, we will use a self-signed certificate: only after registering a DNS record pointing to your IP address you may obtain a trusted third-party certificate.

# Generate a self-signed certificate and its key
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365

Create an example.conf file in /etc/nginx/sites-available/ folder. This folder should contain one file per website you host (yes, the same machine may host several websites!).

# sudo nano /etc/nginx/sites-available/example.conf

server {
  listen 80;
  listen [::]:80;
  server_name example.com www.example.com;
  return 301 https://www.example.com$request_uri;
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
  include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

  server_name example.com;
  return 301 https://www.example.com$request_uri;
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
  include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

  root /var/www/example/wordpress;
  server_name www.example.com;

  location / {
    index index.php;
    try_files $uri/ $fastcgi_script_name /index.php?$args $uri.html;
    expires max;
    # Transform non-php non-file requests into php requests
    if (!-e $request_filename) {
      rewrite ^.*$ /index.php last;
    }
  }

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    try_files index.html $fastcgi_script_name =404;
    set $path_info $fastcgi_path_info;
    fastcgi_param PATH_INFO $path_info;
    fastcgi_index index.php;
    include fastcgi.conf;
    fastcgi_pass unix:/var/run/php/phpn.m-fpm.sock;  # replace phpn.m with your version
  }

}

Link this file in the sites-enabled folder, test the nginx configuration and restart nginx.

sudo ln -s /etc/nginx/sites-available/example.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx.service

With this configuration, all HTTP(S) requests to (www.)example.com are redirected to https://www.example.com, the URL is converted to a file path relative to /var/www/example/wordpress; if a file is found, it is served by nginx; otherwise, the request is processed by php interpreter.

This nginx configuration supports both IPv4 and IPv6.

TLS configuration

HTTPS means “HTTP over SSL/TLS”, where SSL or TLS are security protocols. HTTP requests are plain text, readable by anyone; to convey security to the transmission of data, a cryptographic protocol is used to encrypt the exchanged data. At the beginning of the communication, the client makes sure it is speaking with the right server by encrypting a key using the server’s certificate: only the rightful owner of the server can decrypt with their private key what is encrypted using the public key stated in the trusted third-party certificate. The rest of the communication is encrypted symmetrically with an agreed password.

There are many cryptographic protocols available for HTTPS communications, but only TLSv1.2 and TLSv1.3 are currently considered secure. Supporting only these two may make your website inaccessible for outdated clients, but achieves a stronger security.

Create a /etc/letsencrypt/options-ssl-nginx.conf file and set HTTP header options and encryption accepted protocols and ciphers. Do not trust this example: check out currently secure protocols and ciphers here.

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;

add_header Strict-Transport-Security "max-age=31536000";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy no-referrer;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_ecdh_curve auto;
keepalive_timeout   70;
ssl_buffer_size 1400;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=86400;
resolver_timeout 10;

This configuration file must be included in any nginx server block: if one single server block is missing these options, all blocks may fail to support TLSv1.3 (some versions of Nextcloud, with their own configuration files, tend to break this rule and you will need to fix them manually).

Example of server block including these settings:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    ...

Security advice

  • Do not install unnecessary or untrustworthy WordPress plugins; keep their number as low as possible
  • Keep your operating system, WordPress, php, nginx, MySQL, and all other software up to date
  • Reboot your server after major updates
  • Hide or protect your /wp-login directory to prevent brute-force attacks
  • Use strong passwords and keep them safe; do not use the same password for different services; change them whenever you suspect they may have been exposed
  • Support only secure protocols

Register a DNS record

  • A record: associates the domain (example.com) and its subdomains to one or more IPv4 addresses; when a client queries an IPv4 DNS for a specific subdomain, it will be directed towards the corresponding IP address.
  • AAAA record: the same as A record, but with IPv6 addresses; please notice that not all providers have an IPv6 DNS, so IPv6-only clients may be unable to find your website; this may become an issue as soon as IPv4 gets deprecated, but unfortunately this is not happening soon.
  • If your provider supports CAA records, you may associate your domain with one or more CAs and provide an email to report issues. Only certificates provided by those CAs will be accepted for your domain.
  • MX record: if your plan includes a mail service, this record is set for you by your provider; otherwise, you will have to provide information about mail server(s) associated with your domain; if your VPS is good enough, you may host your own mail server. This record is important to allow email verification.

Example of A record and AAAA record:

//A
//HostName,IPAddress
A,,1.1.1.1  // Your VPS IPv4 address
A,webmail,5.5.5.5 // Your mail server address
A,www,1.1.1.1

//AAAA
//HostName,IPV6Address
AAAA,,:::::::1  // Your VPS IPv6 address
AAAA,www,:::::::1

Get a Let’s Encrypt certificate

Let’s Encrypt is a trusted nonprofit Certificate Authority whose mission is to provide TLS certificates for free, to make the Internet a safer place.

The easiest way to manage certificates in a LEMP environment is using certbot: once you own a domain and control a VPS at the corresponding IP address, just run sudo certbot --nginx: it will take care of everything, from certificate issuing and renewing to nginx HTTPS configuration.

Leave a Reply