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.

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

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.

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

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.

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.

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.

After a minute or so, the instance will change to 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.

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.

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

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.

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.

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

Click the Save button when complete.

Step 4 – Disable IPv6 Networking
In the IPv6 networking section, disable IPv6 networking if it is not required.

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

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

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.

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).

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-{fpm,mysql,curl,gd,imagick,intl,mbstring,mysql,xml,mcrypt,zip,ldap} 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
- Automatically Back Up a Linux Server to Google Drive
- Automate Amazon Lightsail Maintenance Activities Using Bash Script
- Enable Browser Caching Directives in Amazon Lightsail Apache Server
- Hardening WordPress Security Using .htaccess
- How to Migrate an Existing WordPress Site to Amazon Lightsail LAMP
- Manually Create a WordPress Database from the MySQL Client