Install a LAMP Stack Manually on a Debian OS Only Amazon Lightsail Instance

Building and operating a public-facing web server is intimidating. Servers are subjected to nearly constant scanning for vulnerabilities by threat actors regardless of server size, hosted content, or payoff with respect to a hack. As you can easily assess by the length of this guide, there are many steps needed to create a basic web server from an operating system (OS) only image. It is no wonder why there are so many hosting companies providing a sliver of shared hosting space on an underlying server managed by the company.

Why would anyone want to build their own server? Personally speaking, the factors are cost, control, and personal gratification. For the convenience and possibly lower risk of managed hosting, consumers bear an additional financial cost for someone else to manage the server. This convenience also comes with an overall loss in control. In managed hosting environments, consumers are typically restricted to particular software packages/versions with a very limited ability to customize the server configuration. I’m sure for most consumers, the added cost and loss of control are minor considerations when one simply needs a website up and running. Also, not everyone gets personal gratification from building and maintaining a server. To each their own.

Instead of using a managed hosting provider, building a public-facing web server from an OS only image offers substantial control over a website’s operating environment. By creating a server from an OS only image, you have direct control over the installed software packages and versions, the patching schedule, and server security. However, with great power comes great responsibility. You are the only one to blame when the server succumbs to an attack because of a missed patch. If you don’t establish a backup process as part of server operations and the server fails for any reason, then there is usually no path to recovery.

Why start from an OS only image? While there are many prepackaged server images available with most of the software packages required to host a website, e.g., Bitnami, the initial setup speed again comes with a loss of control. As an example, creating a Lightsail instance using one of the available Bitnami server images makes upgrades and patches to the installed PHP version impossible. To upgrade PHP, an entirely new instance must be created using a Bitnami image with the desired PHP version assuming the version is even available in an image. All of the content and custom configurations from the original server must then be transferred to the new server.

Scope

This post is intended to provide a comprehensive, step-by-step guide to establish a Linux server running Apache, PHP, and MySQL/MariaDB, otherwise known as a LAMP stack, starting with only the Debian operating system installed. While other guides typically stop at the point where the basic LAMP software packages are installed, this post additionally covers basic considerations for creating a secure and performant web server such as enabling HTTP/2 and enabling HTTPS with SSL/TLS certificates.

While I am using Amazon Lightsail for this guide, you may alternatively use any other hosting provider that has as a Debian OS only server image option.

Create and Configure the Instance

In this guide, I assume that you have already established an AWS account and that you have access to the AWS Management Console.

Step 1 – Create an OS Only Debian Instance

Log in to the AWS Management Console.

Search for Lightsail and select it from the list of available services.

AWS Management Console – Lightsail Search
AWS Management Console – Lightsail Search

The Lightsail home screen is displayed. Click Instances if it is not already selected.

Lightsail – Home Screen
Lightsail – Home Screen

Click the Create instance button.

The Create an instance screen is displayed.

Review the default Instance location and change it as appropriate. In this guide, I am using the default location of Virginia, Zone A (us-east-1a).

In the Pick your instance image section under Select a platform, select Linux/Unix.

Under Select a blueprint, choose OS Only and then select one of the Debian options. In this guide, I have selected Debian 11.4, however, the instructions should work without modification for any recent Debian version.

Lightsail – Create An Instance
Lightsail – Create An Instance

In the Optional section, the default values are left unchanged. The default SSH key is used and Enable Automatic Snapshots is disabled.

Lightsail – Create An Instance Optional Settings
Lightsail – Create An Instance Optional Settings

In the Choose your instance plan, select the appropriately sized and priced bundle for your needs. In this guide, I have selected the least expensive plan.

Lightstail – Choose Your Instance Plan
Lightstail – Choose Your Instance Plan

In the Identify your instance section, provide a unique name for this instance and set the number of instances to create. In this guide, I have entered the instance name lightsail-test and selected a quantity of 1.

No changes are made to the Key-only tags and Key-value tags sections.

Click the Create instance button once you are satisfied with your selections.

Lightsail – Identify Your Instance
Lightsail – Identify Your Instance

The Lightsail home screen is displayed with the newly created instance listed under the Instances heading. The instance status is displayed as Pending until it is created and running.

Lightsail – Instance Creation Pending
Lightsail – Instance Creation Pending

After a minute or so, the instance will change to Running.

Lightsail – Instance Running
Lightsail – Instance Running

Step 2 – Attach a Static IP

Click on the instance name to open the management options.

Click the Networking heading.

In the IPv4 networking section, a public IP address is displayed. Click Attach static IP.

Lightsail – IPv4 Networking
Lightsail – IPv4 Networking

A prompt to Create and attach a static IP is displayed.

Enter a unique name to identify this static IP within Lightsail. In this guide, I have entered the name lightsail-test-staticip-1.

Click the Create and attach button.

Lightsail – Create and Attach a Static IP
Lightsail – Create and Attach a Static IP

The message Static IP created is displayed. Click the Continue button.

Lightsail – Static IP Created
Lightsail – Static IP Created

Returning to the IPv4 networking section, the static IP is attached to the instance as expected. Since this is now a static IP address, you can specify this IP address in the DNS A record assuming this server will be used to host a domain.

Lightsail – Static IP Attached
Lightsail – Static IP Attached

Step 3 – Restrict IPv4 Access

In the IPv4 Firewall section, edit the SSH rule. Enable the Restrict to IP address option as well as the Allow Lightsail browser SSH option if it isn’t already.

Lightsail – IPv4 Firewall SSH Rule
Lightsail – IPv4 Firewall SSH Rule

Specify one or more IP addresses allowed to connect to the instance through SSH.

Lightsail – IPv4 Firewall IP Addresses
Lightsail – IPv4 Firewall IP Addresses

Click the Save button when complete.

Lightsail – IPv4 Firewall Enabled
Lightsail – IPv4 Firewall Enabled

Step 4 – Disable IPv6 Networking

In the IPv6 networking section, disable IPv6 networking if it is not required.

Lightsail – IPv6 Networking
Lightsail – IPv6 Networking

A prompt to Disable IPv6 for this instance? is displayed. Click the Yes, disable button.

Lightsail – Disable IPv6 For This Instance
Lightsail – Disable IPv6 For This Instance

The status is now IPv6 networking is disabled and the instance can communicate using only the IPv4 protocol.

Lightsail – IPv6 Networking Disabled
Lightsail – IPv6 Networking Disabled

Install and Configure the LAMP Stack

At this point, we have a running Debian instance with a static IP address assigned and basic firewall protection. In this section, we will install the rest of the LAMP stack.

Connect to the Instance Using SSH

From the Lightsail home screen, click on the instance name to open the management options.

Click the Connect heading.

There are two methods available to connect to the instance. The first method uses a browser-based SSH client. This methods opens a browser window that connects to the instance and places you at the shell prompt. The second method uses an SSH client, e.g., PuTTY. I suggest using PuTTY as it is a more capable client. The terminal screenshots in this guide are based on the PuTTY client. For the purposes of this guide, however, the browser-based SSH client is sufficient.

Click the Connect using SSH button to open the browser-based SSH client.

Lightsail – SSH Connection
Lightsail – SSH Connection

Confirm Baseline Disk Usage

After connecting to the instance for the first time and before installing additional software packages, run the df -h command. This is the disk free command with the -h option to display results in human readable powers of 1024, e.g., KB, MB, or GB. This command displays the amount of used and available disk space on the instance. While this step is not necessary, I like to note this information to get a sense of how much storage is used after installing software packages and to track disk usage over time. By recording this baseline, it helps me monitor for software bloat and runaway disk usage.

At the shell prompt, execute the following command:

df -h

The command displays the following output (or similar).

Lightsail – Disk Usage Baseline
Lightsail – Disk Usage Baseline

Upgrade Installed Packages and Dependencies

Next, update the instance’s package list from the Debian software repository using apt update and then download and install the current updates using the apt upgrade command. APT stands for Advanced Package Tool. It handles the installation, management, and removal of software packages on Debian systems.

The sudo command enables users to execute programs with the security privileges of another user, e.g., the superuser. This command is used quite frequently throughout this guide as we are performing many actions that require elevated privileges, e.g., installing software, enabling or disabling system services, etc.

At the shell prompt, execute the following command:

sudo apt update && sudo apt upgrade -y

The -y option allows you to skip confirmation prompts for each upgrade.

The command generates a lot of output as it identifies updates and installs the available upgrades. When the command finishes, look through the text to identify any issues, errors, or notifications that a reboot is required. A reboot can be completed from the Lighstail home screen for the instance or executing the command sudo reboot.

Install Apache HTTP Server

We are now ready to install Apache HTTP Server.

At the shell prompt, execute the following command:

sudo apt install -y apache2

Again, the -y option allows you to skip confirmation prompts.

The command will display a lot of output and complete unceremoniously. Apache HTTP Server is now installed.

Configure Apache

By default, the Apache installation enables several modules to add functionality to the core Apache server. Review the enabled modules and verify that they are absolutely needed. Each enabled module that is not required expands the potential attack surface of the server and should be disabled.

apache2ctl is a command line utility to control Apache on Debian based distributions. The -M option is a synonym for -t -D DUMP_MODULES which shows all loaded modules.

At the shell prompt, execute the following command:

/usr/sbin/apache2ctl -M

The list of loaded modules is displayed. Review the list for any modules that are missing or not needed.

Loaded Modules:
 core_module (static)
 so_module (static)
 watchdog_module (static)
 http_module (static)
 log_config_module (static)
 logio_module (static)
 version_module (static)
 unixd_module (static)
 access_compat_module (shared)
 alias_module (shared)
 auth_basic_module (shared)
 authn_core_module (shared)
 authn_file_module (shared)
 authz_core_module (shared)
 authz_host_module (shared)
 authz_user_module (shared)
 autoindex_module (shared)
 deflate_module (shared)
 dir_module (shared)
 env_module (shared)
 filter_module (shared)
 mime_module (shared)
 mpm_event_module (shared)
 negotiation_module (shared)
 reqtimeout_module (shared)
 setenvif_module (shared)
 status_module (shared)

After reviewing this list, there are several missing modules that we need. The missing modules are not displayed because they are not enabled. These modules are necessary for server performance improvements, security enhancements, and others are simply required for a standard WordPress instance. I disable mpm_prefork and enable mpm_event since the mpm_event module handles higher loads more efficiently. The expires module allows for generation of Expires and Cache-Control HTTP headers. The headers module allows for modification of HTTP request and response headers. The http2 module enables the improved HTTP/2 protocol. The proxy and proxy_fcgi modules enable support for the FastCGI protocol which is required for PHP-FPM which is installed later in this guide. The rewrite module enables rule-based URL redirection which is needed by WordPress on Apache. Finally, the ssl module enables support for the Secure Sockets Layer (SSL) and Transport Layer Security (TLS) protocols, i.e., support for HTTPS. For more information, review the module documentation available through the Apache HTTP Server Documentation Module Index

The a2dismod and a2enmod scripts allow you to disable or enable specified modules within the Apache configuration.

At the shell prompt, execute the following commands as appropriate for your specific requirements:

sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo a2enmod expires
sudo a2enmod headers
sudo a2enmod http2
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
sudo a2enmod rewrite
sudo a2enmod ssl

To activate the configuration changes, Apache requires a restart. The systemctl utility provides for the configuration, management, and interrogation of system services and their status.

At the shell prompt, execute the following command:

sudo systemctl restart apache2

Next, verify that Apache is actively running.

At the shell prompt, execute the following command:

sudo systemctl status apache2

Install PHP

To install PHP, we need to configure APT to make it aware of a repository with the required software packages for PHP.

First, we need to install additional supporting packages allowing APT to access repositories via HTTPS. There is no harm executing this command if these packages are already installed.

At the shell prompt, execute the following command:

sudo apt install -y apt-transport-https ca-certificates gnupg2 software-properties-common

Next, we import the GPG signing keys for the package from the repository. I am using the Sury package repository in this example.

At the shell prompt, execute the following command:

sudo wget -qO /etc/apt/trusted.gpg.d/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg

Finally, we need to add the PHP repository to APT.

At the shell prompt, execute the following command:

echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/php.list

At this point, APT is now aware of a PHP package repository and we will be able to download and install the package in subsequent steps. However, before we install the package, execute another update and upgrade to make sure we are working with the latest packages.

At the shell prompt, execute the following command:

sudo apt update && sudo apt upgrade -y

In this example, we are installing PHP version 8.2 which is the current stable version at the time this guide was written. Always verify the current stable version on the PHP Downloads index and adjust the following commands as needed. Please note that specifying 8.2 will install the latest patch version, e.g., 8.2.2 in this instance. This command also installs some commonly used modules and extensions, e.g., fpm, mysql.

We are installing PHP-FPM (FastCGI Process Manager) since it has features and capabilities for processing PHP while consuming less memory and CPU than other methods. While this is more useful for sites with heavy traffic, I prefer to install it for any instance where PHP requests are processed.

At the shell prompt, execute the following command:

sudo apt install -y php8.2 php8.2-{common,fpm,mysql,curl,igbinary,imagick,intl,mbstring,xml,zip} libapache2-mod-php8.2

Once installed, we need to enable PHP-FPM with Apache. The output from the prior command instructs us to execute the following.

At the shell prompt, execute the following commands:

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.2-fpm

We should also disable any existing PHP modules.

At the shell prompt, execute the following command:

sudo a2dismod php8.2

Next, enable the PHP-FPM service.

At the shell prompt, execute the following command:

sudo systemctl enable php8.2-fpm

Restart the Apache and PHP-FPM services to ensure all of the prior configuration changes are active.

At the shell prompt, execute the following commands:

sudo service apache2 restart
sudo service php8.2-fpm restart

Install MySQL / MariaDB

In this section, we will install the M part of the LAMP stack. So far, we have a Linux instance, running an Apache HTTP Server capable of processing PHP requests. That’s only LAP for those keeping track. As we’ve done at the beginning of each package installation, let’s update the package list and install all available upgrades.

At the shell prompt, execute the following command:

sudo apt update && sudo apt upgrade -y

With the packages up-to-date, install MariaDB which is an open-source fork of MySQL.

At the shell prompt, execute the following command:

sudo apt install -y mariadb-server

MariaDB is now installed. Execute the mysql_secure_installation script to improve the security profile of the basic installation.

At the shell prompt, execute the following command:

sudo mysql_secure_installation

The script prompts with a series of questions. I have highlighted suggested responses in brackets in the following section.

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user. If you've just installed MariaDB, and
haven't set the root password yet, you should just press enter here.

Enter current password for root (enter for none):  [Press enter to continue]
OK, successfully used password, moving on...

Setting the root password or using the unix_socket ensures that nobody
can log into the MariaDB root user without the proper authorisation.

You already have your root account protected, so you can safely answer 'n'.

Switch to unix_socket authentication [Y/n] [n]
 ... skipping.

You already have your root account protected, so you can safely answer 'n'.

Change the root password? [Y/n] [Y]
New password:
Re-enter new password:
Password updated successfully!
Reloading privilege tables..
 ... Success!


By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.

Remove anonymous users? [Y/n] [Y]
 ... Success!

Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.

Disallow root login remotely? [Y/n] [Y]
 ... Success!

By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.

Remove test database and access to it? [Y/n] [Y]
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? [Y/n] [Y]
 ... Success!

Cleaning up...

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Thanks for using MariaDB!

Finally, restart the mysql service to ensure any configuration changes are activated.

At the shell prompt, execute the following command:

sudo systemctl restart mysql

Additional Apache Configurations for HTTP/2 and Enhanced Server Security

In the /etc/apache2/conf-available/ directory, create a new file named custom.conf (or add these directives in the /etc/apache2/conf-available/security.conf file directly).

By default, Apache HTTP Server exposes information about itself in response headers. This information does not need to be exposed and may be considered an information disclosure vulnerability as it allows attackers to profile the server for vulnerabilities.

This configures ServerTokens to only display “Apache” instead of the Apache version number, operating system, and installed modules.

We also disable ServerSignature which would otherwise display server information at the bottom of server generated responses, e.g., 404 error pages.

The configuration also enables support for HTTP/2. Specifically, h2 is the secure variant over TLS and h2c is the cleartext variant over TCP.

The SSLStaplingCache directive enables the cache to store OCSP responses which are included in the TLS handshake. If SSL/TLS (HTTPS) will not be used, then you can exclude this directive.

ServerTokens Prod
ServerSignature Off
FileETag None

<IfModule mod_http2.c>
  Protocols h2 h2c http/1.1
</IfModule>

<IfModule mod_ssl.c>
  SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
</IfModule>

In the /etc/apache2/conf-available/security.conf, apply a comment to the ServerTokens and ServerSignature lines and verify that TraceEnable is Off.

The following section contains the relevant detail from the security.conf file.

#
# ServerTokens
# This directive configures what you return as the Server HTTP response
# Header. The default is 'Full' which sends information about the OS-Type
# and compiled in modules.
# Set to one of:  Full | OS | Minimal | Minor | Major | Prod
# where Full conveys the most information, and Prod the least.
#ServerTokens Minimal
#ServerTokens OS
#ServerTokens Full

#
# Optionally add a line containing the server version and virtual host
# name to server-generated pages (internal error documents, FTP directory
# listings, mod_status and mod_info output etc., but not CGI generated
# documents or custom error documents).
# Set to "EMail" to also include a mailto: link to the ServerAdmin.
# Set to one of:  On | Off | EMail
#ServerSignature Off
#ServerSignature On

#
# Allow TRACE method
#
# Set to "extended" to also reflect the request body (only for testing and
# diagnostic purposes).
#
# Set to one of:  On | Off | extended
TraceEnable Off
#TraceEnable On

Deny Access to the Default Host

By default, Apache has a default host defined to respond to all incoming HTTP requests on port 80. Files located in the /var/www/html directory are publicly accessible. If you try to access http://static-ip-address (replace with the static IP address set during the instance creation), the browser will display the Apache Default Page which contains some information about the server configuration. The default host is defined in the file /etc/apache2/sites-available/000-default.conf. Update the file to include the Location directive which instructs Apache to deny all requests made to the default host listening on port 80.

<VirtualHost _default_:80>
  DocumentRoot /var/www/html

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined

  <Location />
    Require all denied
    Options None
    AllowOverride None
  </Location>
</VirtualHost>

Define a Virtual Host for HTTP Requests

Now that we have blocked all access to the default host, we need to define a virtual host that can respond to HTTP requests. Let’s assume you are the owner of example.com and this server hosts the website for example.com. In the /etc/apache2/sites-available/ directory, create a file name example-com.conf. In this file, we define all the directives for this virtual host. This host will respond to all requests on port 80 where the server name is example.com or the server alias is www.example.com. Resources are served from the DocumentRoot /www/example-com/public_html directory. Since we eventually want all requests to be served securely using HTTPS, the rewrite directive redirects all requests to the HTTPS virtual host which will be defined in the next section. If SSL/TLS (HTTPS) will not be used, then you can exclude this directive.

<VirtualHost _default_:80>
  ServerName example.com
  ServerAlias www.example.com
  DocumentRoot /www/example-com/public_html

  <Directory "/www/example-com/public_html">
    Options FollowSymLinks
    AllowOverride None
    Require all granted
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log vhost_combined

  <IfModule mod_rewrite.c>
    # BEGIN: Enable HTTP to HTTPS redirection
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [NE,R,L]
    # END: Enable HTTP to HTTPS redirection
  </IfModule>
</VirtualHost>

Enable the New Configurations

At this point, we have made several changes to existing configuration files and created a new configuration file for a virtual host. Apache does not automatically enable new configurations so we will need to execute a few commands to enable them. Adjust the file names as needed for your configuration. For reference, the a2enconf command is used to enable an Apache configuration file and the a2ensite enables a virtual host.

At the shell prompt, execute the following commands:

sudo a2enconf custom.conf
sudo a2ensite 000-default.conf
sudo a2ensite example-com.conf
sudo systemctl reload apache2

Install Certbot to Obtain SSL/TLS Certificates for HTTPS

In order to serve content over HTTPS, SSL/TLS certificates are needed. These certificates can be purchased through a well-known certificate authority or you can create free certificates automatically using Certbot. The certificates generated by Certbot use the nonprofit Let’s Encrypt certificate authority.

The Certbot installation uses the Snap software packaging and deployment system. If certbot is already installed through another packaging system, e.g., apt, then we need to remove that installation first.

At the shell prompt, execute the following command:

sudo apt-get remove certbot

Next, install snapd.

At the shell prompt, execute the following command:

sudo apt install snapd -y

To ensure we have the latest version of snapd, we will install and refresh core.

At the shell prompt, execute the following command:

sudo snap install core; sudo snap refresh core

Now that snapd is installed, we can use it to install certbot.

At the shell prompt, execute the following command:

sudo snap install --classic certbot

Finally, create a symbolic link using ln -s to ensure the certbot command is executable.

At the shell prompt, execute the following command:

sudo ln -s /snap/bin/certbot /sbin/certbot

Get SSL/TLS Certificate Using Certbot

For certbot to create certificates for a domain, ownership of the domain will need to be verified. Ensure that the DNS A record for the domain points to the static IP address of the server. This will need to be completed through the domain’s DNS host.

Once the DNS records are up-to-date and propagation is complete, we can use certbot to get a new certificate. Use the -d option to specify the domains and sub-domains applicable to this certificate.

At the shell prompt, execute the following command:

sudo certbot --apache -d example.com -d www.example.com

The script prompts with a series of questions. I have highlighted suggested responses in brackets in the following section using example.com as an example.

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): mail@example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
Account registered.
Requesting a certificate for example.com and www.example.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2023-06-01.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for example.com to /etc/apache2/sites-available/example-com-le-ssl.conf
Successfully deployed certificate for www.example.com to /etc/apache2/sites-available/example-com-le-ssl.conf
Congratulations! You have successfully enabled HTTPS on https://example.com and https://www.example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Since Let’s Encrypt certificates expire every three months, the script schedules a task that automatically renews the certificate before it expires. To test the renewal, execute the certbot command with the renew and --dry-run options and verify that the command is successful.

At the shell prompt, execute the following command:

sudo certbot renew --dry-run

You can also verify that the scheduled task is configured and active using the following command.

At the shell prompt, execute the following command:

systemctl list-timers

Configure a Virtual Host for SSL/TLS (HTTPS) Requests

If you read through the output from the certbot command, you will notice that a new virtual host configuration file was created named /etc/apache2/sites-available/example-com-le-ssl.conf.

Edit the file as needed to adjust the configuration.

At the shell prompt, execute the following command:

sudo vim /etc/apache2/sites-available/example-com-le-ssl.conf

For this domain, I am redirecting all requests made to example.com to www.example.com. Remove the rewrite directives if they are not applicable.

If you recall, we enabled the SSLStaplingCache earlier in the custom.conf file. For this virtual host to use SSL stapling, we enable it in this configuration file using the SSLUseStapling on directive.

I also set the HTTP Strict-Transport-Security (HSTS) response header as part of the virtual host configuration. This header informs clients that the site should only be accessed using HTTPS. Any non-HTTPS requests should be converted to HTTPS.

<IfModule mod_ssl.c>
<VirtualHost _default_:443>
  ServerName example.com
  ServerAlias www.example.com
  DocumentRoot /www/example-com/public_html

  <Directory "/www/example-com/public_html">
    Options FollowSymLinks
    AllowOverride All
    Require all granted
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log vhost_combined

  <IfModule mod_rewrite.c>
    # BEGIN: Enable non-www to www redirection
    RewriteEngine On
    RewriteCond %{HTTP_HOST} !^www\. [NC]
    RewriteRule ^(.*)$ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=permanent,L]
    # END: Enable non-www to www redirection
  </IfModule>

  Include /etc/letsencrypt/options-ssl-apache.conf
  SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
  SSLUseStapling on

  <IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
  </IfModule>
</VirtualHost>
</IfModule>

After completing the configuration changes, we need to enable the virtual host in Apache.

At the shell prompt, execute the following commands:

sudo a2ensite example-com-le-ssl.conf
sudo systemctl reload apache2

Create DocumentRoot Directory to Store Files Served by Apache

As part of the virtual host configurations, the document root was specified as /www/example-com/public_html which does not yet exist. Any files that will be served should be stored in this directory, e.g., WordPress files should be uploaded to this directory.

At the shell prompt, execute the following command to create the directory:

sudo mkdir -p /www/example-com/public_html

After creating or uploading the files into this directory, the ownership and permissions for all of the files need to be adjusted. Since Apache runs using the www-data group/user, we change ownership using the chown command.

At the shell prompt, execute the following commands:

sudo chown -R www-data:www-data /www/example-com/public_html

Finally, we set the permissions for all directories to 755 and all files to 644 recursively starting from /www/example-com/public_html.

sudo find /www/example-com/public_html -type d -exec sudo chmod 755 {} \;
sudo find /www/example/public_html -type f -exec sudo chmod 644 {} \;

Results

After many steps, we have reached the end of the guide and you have full control over a public-facing web server. If you navigate to the domain or static IP address from a browser, the server will respond with the requested files stored in the document root. As I mentioned at the beginning of this post, be sure to establish an appropriate back up and maintenance process for continued server operations.

Other Considerations

phpMyAdmin

Many managed hosting companies provide access to phpMyAdmin so that clients can perform MySQL administrative tasks through a web-based interface. As the server owner, you may certainly install phpMyAdmin, however, I will not cover those steps in this guide as I do not recommend it. In my opinion, installing phpMyAdmin adds unnecessary bloat to a public-facing server and it also increases the attack surface for threat actors. While I had used phpMyAdmin for many years as a consumer of managed hosting, I have since switched to other database administration products that do not directly reside on the server, e.g., HeidiSQL is a free and open-source administration package with the capability to connect to a server using a tunneling protocol (SSH).

Further Reading