Install EC2 Debian 11, PHP8.2, Mariadb, Apache2
This is a stable installation as at May 2025 using x86_64 Debian 11 Linux and hardware architecture on a t3a instance – e.g. t3a.nano or t3a.micro.
If you install on t3 by mistake, you can snapshot and re-attach it to a t3a instance.
I prefer NGINX, but Apache2 is a good starting place. On either platform, WordPress editing sometimes freezes. We do not know why as yet.
I will show an NGINX installation on Debian 12 and AWS Linux2023 separately to this tehnical note.
My example uses Sydney – ap-southeast-2 and subnet ap-southeast-2a. I use GP3 hard disk. Make sure Credit Specification is Standard.
Please subscribe to Debian 11 (x86_64) in the Market Place at $0 cost. (You can try ARM on a t4 instance if you wish.)
I use laurenceshaw.au in the examples. All my Linux commands are via the vi editor. My SSH key is a .pem file on an iMac logging into Linux as root.
Please be reasonably familiar with pre-requisites such as iMac root terminal login, Connecting to the EC2 instance, sudo su once logged in, and use of FileZilla with your .pem key (.ppk on Windows Putty)
Launch an Instance - Configurations
First Linux configurations
On an iMac terminal, previously set up your ability to use root login for the ssh command to work. (Internet search to see how, or view previous articles). These small shell scripts can be helpful when you log into a terminal session and change to root with “sudo su”.
vi ssh.sh #!/bin/sh :>/var/root/.ssh/known_hosts exit [save and exit] chmod 777 ssh.sh [use your own .pem key file name and location, and the ssh command from the EC2 Connect tab.] vi domain.sh #!/bin/sh cd PEM ssh -i "domain.pem" admin@ec2-xx-xx-xx-xx.ap-southeast-2.compute.amazonaws.com exit [save and exit] chmod 777 domain.sh These can assist for quick logins and clearing the ssh keys on your iMac. For example: ./domain.sh
Log into the EC2 instance, and set your environment before working on the instance.
Assumption: you have attached an IP4 address and are able to SSH login. $ sudo su # All commands will be under root. Where needed I will make comments... (Use 1GB swap space. Less will cause issues. Use your own timezone. DOMAIN is you domain name, or possibley something else descriptive) export EXINIT='set noautoindent' export VISUAL=vim echo "vm.swappiness=10" >> /etc/sysctl.conf echo "vm.vfs_cache_pressure=200" >> /etc/sysctl.conf sysctl -w vm.swappiness=10 sysctl -w vm.vfs_cache_pressure=200 dd if=/dev/zero of=/swapfile bs=1024 count=1048576 mkswap /swapfile chmod 0600 /swapfile swapon /swapfile echo "/swapfile swap swap defaults 0 0" >> /etc/fstab a="Australia/Brisbane";export a;echo $a ln -sf /usr/share/zoneinfo/$a /etc/localtime date vi /etc/vim/vimrc.local let skip_defaults_vim = 1 if has('mouse') set mouse=r endif [save and exit] cd ~ vi .bashrc export EXINIT='set noautoindent' export VISUAL=vim export PS1="[\u@DOMAIN: \w]\\$ " alias rm='rm -i' alias cp='cp -i' alias mv='mv -i' [save and exit] cd /home/admin vi .bashrc export EXINIT='set noautoindent' export VISUAL=vim export PS1="[\u@DOMAIN: \w]\\$ " alias rm='rm -i' alias cp='cp -i' alias mv='mv -i' [save and exit] apt update apt upgrade SETUP A BACKDOOR EMERGENCY LOGIN - useful for EC2 serial console login if something has gone wrong. We will add a backup/backdoor user. If you get the sss_cache error shown below, please use the fix shown. Changes are based on building a new site before it goes live. We will use "snoopy" (the dog) as the user name... adduser snoopy (Give snoopy a password then add snoopy to /etc/sudoers - Using the vi editor, go to the end of the file (SHIFT G), and append the entry. Then use :w! to save the entry as it is a read only file.) (Again, all commands are from root user) vi /etc/sudoers snoopy ALL=(ALL) NOPASSWD:ALL [Exit the file after saving with SHIFT ZZ] (Add the user to groups admin and root: (for Linux 2023, it is wheel and root)) usermod -aG admin snoopy; usermod -aG root snoopy (We will make a copy of a good verion of /home/admin/.ssh to /home:) cd /home/admin cp -pr .ssh ../SSH_BACKUP This completes the creation of a backup user that you can use in an emergency on the EC2 Contact console. IF YOU GET THIS ERROR: ------------------------------- [sss_cache] [sysdb_domain_cache_connect] (0x0010): DB version too old [0.22], expected [0.23] for domain implicit_files! Higher version of database is expected! In order to upgrade the database, you must run SSSD. Removing cache files in /var/lib/sss/db should fix the issue, but note that removing cache files will also remove all of your cached credentials. Could not open available domains -------------------------------- To fix this, do the following: cd /var/lib/sss/db rm * sss_cache -E Then add the backup/backdoor user.
We will next add the Linux packages. Keep in mind, after doing all the work, stop and restart the instance and if adding major components, it can help to do the commands: sync;sync;reboot
Install Packages
I’m providing quite a number of packages to cover WordPress and general use of apps. I will show how to use letsencrypt, but in the immediacy will show use of a paid SSL certificate for laurenceshaw.au.
Preliminary work – your CAA records…
In your DNS settings, if using Comodo or Sectigo, add two CAA records. If these fail in the future, just contact the supplier to see what the records should be:
CAA 0 issue sectigo.com
CAA 0 issue digicert.com
If using Letsencrypt (certbot): 0 issue letsencrypt.org
If a paid certificate fails due to having letsencrypt in the DNS records, you could configure an authentication record instead, or temporarily remove the letsencrypt record. It should still be possible hoserver to install letsenscypt SSL without its CAA record.
Again, use root login to the EC2 instance:
PRELIMINARY WORK for a PAID SSL Certificate (and add CAA records to the DNS) Let's say we have a paid SSL certficate that you have edited to include all the chaining (this is a separate topic) Upload the .crt and .key files with FileZilla to /home/admin cd /etc/ssl/certs cp /home/admin/*crt . cd ../private cp /home/admin/*key . This is now out of the way. (Letsencrypt will be covered, but for now we use this.) PRELIMINARY APT installations with KEY WARNING FIXES - this is for Debian 11. Should be ok for Debian 12? apt update Sometimes we do or do not get an error: Get:5 https://packages.sury.org/php bullseye InRelease [7551 B] Err:5 https://packages.sury.org/php bullseye InRelease The following signatures couldn't be verified because the public key is not available: NO_PUBKEY B188E2B695BD4743 Reading package lists... Done W: GPG error: https://packages.sury.org/php bullseye InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY B188E2B695BD4743 E: The repository 'https://packages.sury.org/php bullseye InRelease' is not signed. N: Updating from such a repository can't be done securely, and is therefore disabled by default. N: See apt-secure(8) manpage for repository creation and user configuration details. To fix this: apt autoclean apt-key adv --fetch-keys 'https://packages.sury.org/php/apt.gpg' YOu may see this message: Key is stored in legacy trusted.gpg keyring (/etc/apt/trusted.gpg) We will fix this in a moment. It does not stop updates from working though. apt update apt upgrade If still not working, try these commands first and re-attempt the fix above: apt -y install software-properties-common ca-certificates lsb-release apt-transport-https sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' wget -qO - https://packages.sury.org/php/apt.gpg | sudo apt-key add - apt -y install curl gnupg2 ca-certificates lsb-release debian-archive-keyring If there was not an error, still do the above 4 commands. apt update apt upgrade If you see the message apt autoremove, it is okay to do it. I know this is not good, but it seems not to happen on all installations. Fixing the legacy warning... You will see at the top of the output a warning similar to this: apt-key list Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)). /etc/apt/trusted.gpg -------------------- pub rsa3072 2019-03-18 [SC] [expires: 2026-02-04] 1505 8500 A023 5D97 F5D1 0063 B188 E2B6 95BD 4743 uid [ unknown] DEB.SURY.ORG Automatic Signing Key <deb@sury.org> sub rsa3072 2019-03-18 [E] [expires: 2026-02-04] The fix is simple: (after you execute apt update the warning will not be there. If issues, use Google Advanced Search to assist on the exact error you get.) cd /etc/apt cp trusted.gpg trusted.gpg.d apt update apt upgrade ATP PACKAGE INSTALLATIONS now that the warnings and errors are fixed. A t3a.micro instance installs much faster than a t3a.nano instance. Check you did install with GP3 disk, and you used standard credit specifications - see the EC2 top right menu. These kinds of steps are a learning curve, but should be veery familiar if by the time you start offering the service to others. apt -y install gnupg apt -y install php8.2 Re-run this command to make sure gpung2 is insalled: apt -y install curl gnupg2 ca-certificates lsb-release debian-archive-keyring apt -y install php8.2-cli php8.2-mbstring php8.2-xml php8.2-common php8.2-curl php8.2-imap php8.2-bz2 apt -y install mariadb apt -y install php8.2-mysqli php8.2-fpm gcc libjpeg* zip php8.2-zip apt -y install php8.2-gd a2enmod proxy_fcgi setenvif a2enmod ssl a2enconf php8.2-fpm a2ensite default-ssl a2enmod rewrite systemctl reload apache2 apt -y install libgd-tools ipset For certbot/lets encrypt: (we do not install the certbot-apache plugin, and we only use pip for updating letsencrypt certificated) apt -y install python3-venv apt -y install php8.2-xmlrpc php8.2-soap php8.2-intl python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -s /opt/certbot/bin/certbot /usr/bin/certbot apt -y install certbot
Install and Configure Mariadb, various PHP, phpMyAdmin
NOTE: I am not installing memcached for apache2. I do however install it for Nginx. I never install ACPU - it has been too problematic for me. apt install mariadb-server mysql_secure_installation "Enter current password for root" (enter for none): OK, successfully used password, moving on... "Switch to unix_socket authentication [Y/n]" n "Change the root password?" [Y/n] Y (nominate your database password) Y for the remaining questions] systemctl stop mariadb systemctl start mariadb systemctl enable mariadb systemctl enable apache2 systemctl enable php8.2-fpm VARIOUS CONFIGURATIONS cd / find . -name php.ini -print ./etc/php/8.2/fpm/php.ini ./etc/php/8.2/cli/php.ini ./etc/php/8.2/apache2/php.ini cd /etc/php/8.2/fpm Use your own timezone, and modify values to your own preference. If later uploading .sql files through phpMyAdmin, the max file sizees below will apply as a limit. I prefer 512MB for memory_limit. Others may use 128 or 256. I prefer upload_max_filesise and post_max_size as 100MB. cp -p php.ini php.ini.bak vi php.ini date.timezone = Australia/Brisbane max_execution_time = 300 max_input_time = 600 max_input_vars = 2500 memory_limit = 512M post_max_size = 50M upload_max_filesize = 50M [save and exit] cd / find . -name www.conf -print ./etc/php/8.2/fpm/pool.d/www.conf cd /etc/php/8.2/fpm/pool.d cp -p www.conf www.conf.bak Some fo the following values will already be correct: vi www.conf user = www-data group = www-data listen = /run/php/php8.2-fpm.sock listen.owner = www-data listen.group = www-data ;listen.mode = 0660 listen.mode = 0660 ; pm = dynamic pm = ondemand pm.max_children = 75 pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 35 pm.process_idle_timeout = 10s; pm.max_requests = 500 Then at the bottom of the file: (use the same memory value you had in php.ini) php_admin_value[error_log] = /var/log/fpm-php.www.log php_admin_flag[log_errors] = on php_admin_value[disable_functions] = exec,passthru,system php_admin_flag[allow_url_fopen] = off php_admin_value[memory_limit] = 512M [save and exit] We add the emergency lines. You can place these lines anywhere near the commented lines for "emergency". This helps prevent memory leaks, and ability to gracefully use systemctl reload php8.2-fpm on crontab once a night. cd .. cp -p php-fpm.conf php-fpm.conf.bak vi php-fpm.conf emergency_restart_threshold = 10 emergency_restart_interval = 1m process_control_timeout = 60s [save and exit] crontab is already installed on Debian 11. (from memory, I think it is 'apt install cron' on Deb12. I use deb11 at the moment as. know it is stable) Let's add "reload" for php8.2-fpm to crontab: crontab -e 15 0 * * * /usr/bin/systemctl reload php8.2-fpm >/dev/null 2>&1 [save and exit] cd /etc/php/8.2/mods-available cp -p opcache.ini opcache.ini.bak vi opcache.ini zend_extension=opcache.so opcache.jit=off opcache.enable=1 opcache.enable_cli=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=4000 [save and exit] apt update apt upgrade sync;sync;reboot Wait a bit and log back in as root. After all configs are done, please use the EC2 console to stop and start the instance for a "clean slate". cd /var/ chmod 2775 www cd www chmod 2775 html cd html echo " < ?phpZphpinfo(); ? > "|sed 's/ //g'|sed 's/Z/ /g' > info.php Create a simple index.html file (you can remove it after installing WordPress). Use your own domain name. vi index.html BONJOUR DOMAIN [save and exit] cd /var/www/html chown www-data * chgrp www-data * chmod 664 * We are not yet ready to test info.php or the domain name as we have to install SSL into apache2. We will do this shortly. We can however next install phpMyAdmin to manage the database. cd /usr/share wget https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.tar.gz ls tar xvf phpMyAdmin-latest-all-languages.tar.gz rm phpMyAdmin-latest-all-languages.tar.gz ls Use the file name, e.g.: mv phpMyAdmin-5.2.2-all-languages phpMyAdmin cd phpMyAdmin mkdir tmp chmod 777 tmp cp -p config.sample.inc.php config.inc.php vi config.inc.php Search for the blowfish line and insert an appropriate value in the single quotes. I use the following blowfish generator: (paste the value into your line) https://phpsolved.com/phpmyadmin-blowfish-secret-generator/?g=[insert_php]echo%20$code;[/insert_php] Paste the generated value into the blowfish value. For exmample: $cfg['blowfish_secret'] = 'nzpC-qoCt}t/yTKOp5w0o,ULnX,xdKny'; Then after the SaveDir line, add TempDir... $cfg['SaveDir'] = ''; $cfg['TempDir'] = '/tmp'; [save and exit] cd /var/www/html ln -s /usr/share/phpMyAdmin phpMyAdmin
Freezing Problems with mariadb:
It is sometimes nigh impossible to find where the system freezes when editing or displaying a page even after we have given “good” values in our configurations.
phpMyAdmin has a statistics page that graphically can show all is ok. And Amazon EC2 can show CPU, disk writes, RAM all good.
I don’t have an answer to this, but this may help:
cd /etc/mysql cp -p my.cnf my.cnf.bak vi my.cnf [mysqld] innodb_buffer_pool_size=512M optimizer_search_depth=0 log_error=/var/log/mariadb-error.log log_warnings=9 [save and exit] At least we now have an error log. You could try omitting the pool size and optimizer lines. These configurations were found by users on public forums relating to a Help Desk app where the system kept freezing, even though no errors, slow logs, or graphical overloads were shown.
We now wish to add a helpful shell scripts to restart services:
cd /home/admin vi restart.sh #!/bin/sh systemctl restart apache2 systemctl restart mariadb systemctl reload php8.2-fpm swapoff -a swapon -a free -m exit [save and exit] chmod 777 restart.sh chown root restart.sh chgrp admin restart.sh This helps when testing. If using crontab, you could use reload instead of restart. We use reload as our preference for php. swapoff/on is also optional but I use it. You can also use systemctl status -l SERVIVCE, e.g. /usr/bin/systemctl status -l apache2 to make sure no errors or where errors are being notified. All log files are under /var/log - e.g. /var/log/apache2. Please check all of these out.
We will now show the optional but helpful use of the Amazon “aws” command to create backups and store in S3 buckets. Then we will address SSL.
phpMyAdmin basics
Using phpMyAdmin is another learning curve.
These configurations make you ready to install WordPress.
Assuming you made a softlink, e.g.:
cd /var/www/html
ln -s /usr/share/phpMyAdmin phpMyAmdin
Then log into phpMyAdmin as root user and password.
https://my_domain.com/phpMyAdmin
Fix the initial error message at the bottom of the screen where it says “find out why”. Just click on the link and follow the fix.
The screen shots below are fairly old, so use utf8mb4_general_ci or utf8_general_ci instead of Latin.
aws software - optional but helpful
Amazon uses the aws command to access S3 buckets. This is useful for storing backup files and databases.
In other notes, I have shown how to create access to S3 buckets from your Linux instance using an IAM role. You can then add that to the EC2 instance from the EC2 console top right menu in your region. (Actions > Security > Modify IAM Role)
To create the Role if not already done, go to IAM > Roles > Add Role
Select Trusted Entity Type: Aws Service, Use Case: S3, then Permissions Policies:
AdministratorAccess
AmazonS3FullAccess
AmazonSESFullAccess
CloudWatchFullAccessV2
The Trust Relationships tab will look like this:
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Principal”: {
“Service”: “ec2.amazonaws.com”
},
“Action”: “sts:AssumeRole”
}
]
}
When completed, AWS IAM will give you a public and private key. I keep these in a spreadsheet, as you must not lose these.
Then add it to the instance from the Actions pop-down menu as shown above.
To add this to the instance:
cd ~ mkdir .aws cd .aws You must have the empty, two blank lines as shown below. Use the Keys you were given when creating the IAM role. vi config [default] aws_access_key_id = AKIA4......... aws_secret_access_key = 3vvWqH......... region = ap-southeast-2 [save and exit] Press the Enter key for each response during the next configuration: aws configure
Here are two shell scripts that show the use of the aws command. You can modify later when you have a WordPress site, assuming under /var/www/html, and a database with a user created in phpMyAdmin. These can be run via crontab. The shell scripts will use a bucket you previously created in your region. As a side-note, I use Oregon for all email buckets for Australia.
aws COMMAND - THIS IS OPTIONAL BUT VALUABLE - perhaps return to it later cd /home/admin vi info.log [save and exit - just add one blank line] chmod 777 info.log; chown root info.log; chgrp admin info.log Use your own S3 bucket (e.g. create one in your region), website directories and names: vi aws.sh #!/bin/sh d=`date | awk '{print $2,$3,$NF}'|tr " " "-"` echo backups $d >> /home/admin/info.log tar_html() { cd /var/www/ tar cvf domain.tar ./html # if enough disk space, you can gzip the tar file to save on S3 Bucket space: # gzip domain-$d.tar # aws s3 mv domain-$d.tar.gz s3://domain/domain-$d.tar.gz # if not using gzip: aws s3 mv domain-$d.tar.gz s3://domain/domain-$d.tar } # call the functions - you could have multiple functions for multiple domains and backups, hence the use of functions is easier tar_html exit [save and exit] vi awsdb.sh #!/bin/sh d=`date | awk '{print $2,$3,$NF}'|tr " " "-"` cd /home/admin echo database backups $d >> /home/admin/info.log # Your domain database - these can be any names you wish to use db_domain() { cd /home/admin mysqlcheck --user=DB_USER --password=DB_PASSWORD DB_NAME >> /home/admin/info.log mysqldump --user=DB_USER --password=DB_PASSWORD DB_NAME >> /home/admin/DB_NAME-$d.sql # You can gzip if you wish. This example will not do it. aws s3 mv DB_NAME-$d.sql s3://domain/.DB_NAME-$d.sql } db_domain exit [save and exit] You would have set DB_USER in phpMyAdmin, along with the DB_NAME and DB_PASSWORD. For example if the user is "snoopy", the database name is "snoo" and you set the password to "H0und#d0g", mysqldump --user=snoopy --password=H0und#d0g snoo >> /home/admin/DB_NAME-$d.sql It may seem labourious but it is highly useful. You can of course configure without the aws command and S3 buckets for now. If you configure all this, you can test it. Make sure your IAM role was added in the EC2 console. Debian configurations on other platforms like Akami/Linode can install aws software as well, but a few trial and errors if you do that for the first time, using AWS documentation. aws s3 ls s3://domain/ Notice how we use the trailing forward slash to list anything. You can also create subdirectories, such as domain/sub or domain/.sub. For example, s3 ls s3://snoopy.me/.backups/ AWS doco may suggest use of a line like this: aws configure < /home/admin/aws.txt where aws.txt has the same lines we put in ~/.aws/config.
Paid SSL on apache2 for single or multiple domains - Part 1
Obtain your SSL certificate. One way to create the CSR is to open a free account with CleanTalk, and go through the process of creating a certificate, then download or copy the .CSR information, and the .KEY file. (Dashboard > Services > SSL Dashboard)
Here is an example screenshot. When you pay for a certificate e.g. from Comodo Store, (Sectigo for CAA records), you should have your admin@domain email working. Otherwise use the options for a DNS authentication record. I prefer to get emails working first. In the example below, use your ABN name or your registered business name (or trust name – trusts need trust deed documentation for a .com.au or .au top level domain registration.) Just use “IT” for the organisation Unit.
Click Generate, and download the file, then close the browser window or back-step to that you do not actually create it.
In the above example I downloaded laurenceshaw.au.zip. The zip file contains, laurenceshaw_au.csr and laurenceshaw_au.key.
The key file can be kept ad infinitum without renewing it. You transfer this file on Debian Apache2 to /etc/ssl/private.
We use the .csr file at Comodo Store for the lowest cost PositiveSSL(DV), $11.95 AUD at time of writing.
*** Make sure you uncheck any check box for added services they may add by default. ***
Make sure you have added the DNS CAA rcords:
0 issue sectigo.com
0 issue digicert.com
It is best not to have letsencrypt.org as a CAA record if using Paid SSL, or the process can fail and you have to phone their support. If using both free and paid SSL, use the DNS authentication method that Comodo offers. Letsencrypt should be okay without a record if push comes to shove.
Go through the process by supplying the content to Comodo from the .csr file you made. Once you know how to do this, it is easy thereafter.
You will have to copy a key they send you in an admin@domain email if using the CAA records, then click and verify/paste the key.
The Comodo Store panel will show the status of the certificate. Once okay, you can download the zip file or use an admin@ email they send you to the domain you are registering.
Once you get the file, make sure the CAA records are put in place into the DNS, regardless.
Then you have to manually edit the .crt file (CRT) they sent you. Make a copy so you can edit the original.
Append to it the contents of a file that is names similar to this: SectigoRSADomainValidationSecureServerCA.crt
Then append again the contents of something like: USERTrustRSAAAACA.crt and save.
Do not use a Root certificate – e.g. a name like AAACertificateServices.
If you append content in the wrong order, later when testing at sslabs.com you will see a warning.
Then upload your modified file, e.g. laurenceshaw_au.crt and place in /etc/ssl/certs. I use Filezilla and simply “cp” the file.
Out of interest, Linux2023 would use /etc/pki/tls/certs and private.
When we configure Apache, we can later test the certificate. It will cover domain and www.domain. E.g. laurenceshaw.au and www.laurenceshaw.au
I do not go into more complex use of certificates, or more expensive ones. If you are actually making a lot of sales, you can look at that.
Here are the steps to configure apache2:
Add or modify stanzas as shown here. Lets add a standard domain under /var/www/html, perhaps placing it below whte /var/www stanza. cd /etc/apache2 vi apache2.conf <Directory "/var/www/html"> Options Indexes FollowSymLinks AllowOverride All Require all granted DirectoryIndex index.php index.html </Directory> [save and exit] Add or modify the following, using your own domain name. Again, we are assuming a paid SSL certificate for the moment. I am showing the full content below. cd /etc/apache2/sites-available vi 000-default.conf <VirtualHost *:80> ServerName laurenceshaw.au ServerAlias www.laurenceshaw.au Redirect permanent / https://laurenceshaw.au/ RewriteEngine on RewriteCond %{SERVER_NAME} =laurenceshaw.au RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> [save and exit] In the above example, the Rewrite lines are uncommented, as the SSL will be installed into /etc/ssl/... If you install with Letsencrypt, you must comment the lines out as it is not possible to redirect to https:// and letsencrypt will fail to install. Here is an example of a stanza you could append after the above configurations for a subdomain called tech.laurenceshaw.au, assuming we will use Letsencrypt. You could also use a different top level domain, e.g. mygreatestwebsite.com.au <VirtualHost *:80> ServerName tech.laurenceshaw.au # Redirect permanent / https://tech.laurenceshaw.au/ # RewriteEngine on # RewriteCond %{SERVER_NAME} =tech.laurenceshaw.au # RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] ServerAdmin webmaster@localhost DocumentRoot /var/www/tech ErrorLog ${APACHE_LOG_DIR}/error.log </VirtualHost> You would uncomment these lines after the certbot command installs the free SSL. Debian 11 should have enabled the modules you need, including rewrite. (I noticed it did not in Debian12) If we want extra security we should enable the headers module. We can then add some extra code in. /var/www/html/.htaccess later on. a2enmod headers The .htaccess code we could use is: (if you get strange behaviour, remove it.) Do not add the line as some forums use: Header always set Referrer-Policy: "strict-origin" as this will stop a lot of things from working. Header unset X-Powered-By Header always set X-XSS-Protection: "1; mode=block" Header always set X-Content-Type-Options: "nosniff" Header always set X-Frame-Options: "SAMEORIGIN" Header always set Content-Security-Policy: "object-src 'none'; base-uri 'none'; frame-ancestors 'self'; form-action 'self';" Header always set Permissions-Policy: "autoplay=(), encrypted-media=(), fullscreen=(), geolocation=(), microphone=(), midi=()" Header always set Clear-Site-Data: "*" Header always set Strict-Transport-Security: "max-age=31536000; includeSubDomains; preload" Out of interest, if using the Akamai/Linode service, you have to add the AAA record as well as the A record for domain names. Letsencrypt would notice if it is missing on that system.
We now configure the default-ssl.conf file. I avoid failures by having all my configs in 000-default.conf and default-ssl.conf.
These files are by default listed in /etc/apache2/sites-enabled. If not, create a soft link.
For instance, you should see under sites-enabled:
ls
lrwxrwxrwx 1 root root 35 Apr 30 10:14 000-default.conf -> ../sites-available/000-default.conf
lrwxrwxrwx 1 root root 35 Apr 30 10:19 default-ssl.conf -> ../sites-available/default-ssl.conf
Let us say default-ssl.conf was missing (!) We can fix by this command:
If a default file is missing from sites-enabled - this is an example only. cd /etc/apache2/sites-enabled ln -s ../sites-available/ssl-default.conf ssl-default.conf ls lrwxrwxrwx 1 root root 35 Apr 30 10:14 000-default.conf -> ../sites-available/000-default.conf lrwxrwxrwx 1 root root 35 Apr 30 10:19 default-ssl.conf -> ../sites-available/default-ssl.conf You can search the internet for the other common a2... commands if needed. e.g. "How to enable a site on apache2" or enable a mod, or enable a conf file.
The default-ssl.conf file content, for a PAID SSL certificate (using my example domain of laurenceshaw.au from Comodo Store)
In this example, recall that the 000-default.conf file does not need to comment out the Rewrite rules, whereas Letsencrypt would need to.
We would not add the default-ssl.conf file configurations for a domain intended for Letsencrypt at this point. We would install the SSL, then update default-ssl.conf to be correct, and then remove the commented Rewrite lines from 000-default.conf – then restart apache2.
I will show the full content, so add what is missing, with your own domain name and certificate locations. cd /etc/apache2/sites-available cp -p default-ssl.conf default-ssl.conf.bak The ssl module should be enabled already. Otherwise "a2enmod ssl" vi default-ssl.conf <IfModule mod_ssl.c> <VirtualHost _default_:443> ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on SSLCertificateFile /etc/ssl/certs/laurenceshaw_au.crt SSLCertificateKeyFile /etc/ssl/private/laurenceshaw_au.key SSLProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLProxyProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 SSLProxyCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA SSLHonorCipherOrder on SSLCompression off SSLInsecureRenegotiation Off SSLSessionTickets Off SSLOpenSSLConfCmd ECDHParameters secp384r1 SSLOpenSSLConfCmd Curves secp384r1 # you will see a stanza for FilesMatch that we do not edit <FilesMatch "\.(cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> </VirtualHost> # YOU WOULD PUT MULTI-DOMAIN STANZAS IN HERE </IfModule> [save and exit] Restart apache and test the website works. You can have a simple index.html file you can delete later, or the info.php file. E.g. https://laurenceshaw.au/info.php Test the index.html file you create so that you know the system should be fully working. Then test phpMyAdmin, where previously you installed it, and had a softlink under /var/www/html lrwxrwxrwx 1 root www-data 21 Dec 3 16:27 phpMyAdmin -> /usr/share/phpMyAdmin https://laurenceshaw.au/phpMyAdmin You should not need a phpMyAdmin.conf file. Now.... lets see what a multi-domain config looks like. You would keep the Rewrite lines commented out in 000-default.conf, and not have any entry for the new domain in default-ssl.conf. Once certbot adds the certificate, remove the comments from 000-defaultconf, then append something like this after the previous virtual host stanza: <VirtualHost *:443> ServerName tech.laurenceshaw.au:443 DocumentRoot /var/www/tech SSLEngine on SSLCertificateFile /etc/letsencrypt/live/tech.laurenceshaw.au/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/tech.laurencehaw.au/privkey.pem SSLProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLProxyProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 SSLProxyCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA SSLHonorCipherOrder on SSLCompression off SSLInsecureRenegotiation Off SSLSessionTickets Off SSLOpenSSLConfCmd ECDHParameters secp384r1 SSLOpenSSLConfCmd Curves secp384r1 </VirtualHost> And restart apache2. Then test the web browser. Test your SSL with https://www.ssllabs.com/ssltest/ It will give an A rating. If you put in the Headers for .htaccess you may get an A+ rating. You really only need A.
Free SSL - Letsencrypt (certbot) - apache2 for single or multiple domains - Part 2
LetsEncrypt can be tricky. To do a fresh installation, you usually have the CAA record (but not critical), as “0 issue letsencrypt.org”.
You must have your 000-default.conf file with no lines referencing a rewrite to https, as https does not yet exist.
You move default-ssl.conf to default-ss.conf.bak so that there is no SSL configuration involved.
You use –dry-run until you get a success message.
You then un-comment the rewrite rules, and edit the ssl conf file to have the domain name for port 443, and add the certificate paths. Restart apache2 and you are good to go by testing your https://domain.com site.
Here are example commands:
cd /etc/apache2 cd site-av* pwd vi 000-default.conf (Uncomment any Rewrite redirections to https for the proposed domain. See Part 1 above for the 000-default.conf file contents) [save and exit] mv default-ssl.conf default-ssl.conf.bak systemctl restart apache2 (Use your own email that you like to have for registering domains with letsencrypt, and use your domain, subdomain, or other domain as shown. The example has me@gmail.com and laurenceshaw.au with /var/www/html as the website directory.) /usr/bin/certbot certonly --non-interactive --agree-tos -m me@gmail.com -d laurenceshaw.au --webroot -w /var/www/html --dry-run - The dry run was successful. Now remove the ---dry-run and execute it. Then edit the .conf files, as shown in Part 1 above. I'll give contents here in case it helps. vi 000-default.conf (remove comments from the domain's rewrite lines.) <VirtualHost *:80> ServerName laurenceshaw.au ServerAlias www.laurenceshaw.au Redirect permanent / https://laurenceshaw.au/ RewriteEngine on RewriteCond %{SERVER_NAME} =laurenceshaw.au RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> [save and exit] If you were working on a multi-domain or subdomain, you'd have a stanza(s) below this that look like this after SSL is installed and you removed the comments rewrites: <VirtualHost *:80> ServerName tech.laurenceshaw.au Redirect permanent / https://tech.laurenceshaw.au/ RewriteEngine on RewriteCond %{SERVER_NAME} =tech.laurenceshaw.au RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] ServerAdmin webmaster@localhost DocumentRoot /var/www/tech ErrorLog ${APACHE_LOG_DIR}/error.log </VirtualHost> Now we fix the default-ssl.conf file: cp -p default-ssl.conf.bak default-ssl.conf vi default-ssl.conf <IfModule mod_ssl.c> <VirtualHost _default_:443> ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on SSLCertificateFile /etc/letsencrypt/live/laurenceshaw.au/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/laurenceshaw.au/privkey.pem SSLProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLProxyProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 SSLProxyCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA SSLHonorCipherOrder on SSLCompression off SSLInsecureRenegotiation Off SSLSessionTickets Off SSLOpenSSLConfCmd ECDHParameters secp384r1 SSLOpenSSLConfCmd Curves secp384r1 <FilesMatch "\.(cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> </VirtualHost> </IfModule> [save and exit] systemctl restart apache2 (If errors, see systemctl status -l apache2) Note that there are two long lines, SSLCipherSuite and SSLProxyCipherSuite that must be one line each, not accidentally split into multiple lines by your editor. Now we can add an example of a multi-domain. Append after the above </VirtualHost> stanza, and before </If Module> the following: (with your own domains that configiured SSL correctly) vi default-ssl.conf <VirtualHost *:443> ServerName tech.laurenceshaw.au:443 DocumentRoot /var/www/tech SSLEngine on SSLCertificateFile /etc/letsencrypt/live/tech.laurenceshaw.au/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/tech.laurenceshaw.au/privkey.pem SSLProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLProxyProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 SSLProxyCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA SSLHonorCipherOrder on SSLCompression off SSLInsecureRenegotiation Off SSLSessionTickets Off SSLOpenSSLConfCmd ECDHParameters secp384r1 SSLOpenSSLConfCmd Curves secp384r1 </VirtualHost> [save and exit] Restart apache2. I don't like adding configuration files outside these default files. I've found in the past added files do not always work.
Free SSL - Letsencrypt (certbot) Auto-renewal script - Part 3
We now need a shell script to auto-renew Lets Encrypt
Replace with your own values as mentioned in Part 2 above.
cd /home/admin vi certbot.sh #!/bin/bash d=`date` c1=`head -1 /home/admin/certbot.dat` c1=$(expr $c1 + 1) if [ "$c1" = "65" ] ; then echo "0" > /home/admin/certbot.dat echo "Certbot Renewal" $d >> /home/admin/info.log /usr/bin/certbot -v certonly --non-interactive --agree-tos -m me@gmail.com -d laurenceshaw.au --webroot -w /var/www/html >/dev/null 2>&1 sudo /usr/bin/systemctl reload apache2 >/dev/null 2>&1 sudo /usr/bin/openssl x509 -noout -dates -in /etc/letsencrypt/live/laurenceshaw.au/cert.pem >> /home/admin/info.log else echo "Certbot day $c1 of 65" >> /home/admin/info.log echo $c1 > /home/admin/certbot.dat fi sleep 10 d=`date` c1=`head -1 /home/admin/certbot_tech.dat` c1=$(expr $c1 + 1) if [ "$c1" = "65" ] ; then echo "0" > /home/admin/certbot_tech.dat echo "Certbot Renewal" $d >> /home/admin/info.log sudo /usr/bin/certbot -v certonly --non-interactive --agree-tos -m me@gmail.com -d tech.laurenceshaw.au --webroot -w /var/www/tech >/dev/null 2>&1 sudo /usr/bin/systemctl reload apache2 >/dev/null 2>&1 sudo /usr/bin/openssl x509 -noout -dates -in /etc/letsencrypt/live/tech.laurenceshaw.au/cert.pem >> /home/admin/info.log else echo "Certbot day $c1 of 65" >> /home/admin/info.log echo $c1 > /home/admin/certbot_tech.dat fi exit [save and exit] You could of course have these stanzas as shell script functions. e.g. domain() { insert code lines here.... } dubdomain() { insert code lines here... } domain subdomain exit This way you can manage lots of domains simply by commenting our those you don't want at any particular time. Insert a blank line into info.log Add a single line to certbot.dat and in this example also to certbot_tech.dat with the numeral 0 at the top as the only line. chmod 777 info.log certbot.* chown root certbot* info.log chgrp admin certbot* info.log crontab -e 15 1 * * * /home/admin/certbot.sh [save and exit] *** If your certbot.dat files are somehow corrupted with the wrong numerals, you can use the line (for your domain): *** /usr/bin/openssl x509 -noout -dates -in /etc/letsencrypt/live/laurenceshaw.au/cert.pem and work out how many days the certificate has been running, and put into that single top line of the .dat file. Otherwise, if the renewal fails, you have to go through the whole process again of editing the 000-default.conf and default-ssl.conf files. We use 65 days (approx) for renewals. Less than this usually fails. The shell script is not checking for failure or fixing the .dat files.) We do not install the certbot apache plugin. We only use the above commands. You could install postfix and sendmail scripting if you wish, to notify if the SSL certificate has expired. A fair bit of work to do that. You can also monitor your website with Uptime Robot, but that does not check for SSL working.
Adding WordPress
You do need to learn how to use https://your_domain.com/phpMyAdmin.
See the section above on “phpMyAdmin basics”.
We can add a database now usually as utf8mb4_general_ci or utf8_general_ci.
Then we add a user for administrating it – username, localhost, password, and grant all permissions to that database.
If you do not use phpMyAdmin you need to see other articles or documents from Amazon’s installation of WordPress.
The default phpMyAdmin installation will show at the bottom of the screen a missing database that you simply click on to install.
Let’s assume you have this done.
Recall you would have used “ln -s /usr/share/phpMyAdmin phpMyAdmin” to link phpMyAdmin into your /var/www/html directory (or where you choose to have it)
Upload WordPress to your site, unzip and place the contents into /var/www/html or where you need for the domain.
e.g.
cd /var/www/html unzip wordpress-6.8.1-en_AU.zip cd wordpress mv * .. cd .. ./chdir.sh. ---> this is a script shown below.
We need a shell script to change permissions:
cd /var/www/html vi chdir.sh #!/bin/sh chown -R www-data * chgrp -R www-data * find . -type d -exec chmod 2775 {} \; find . -type f -exec chmod 0664 {} \; if [ -f "./.htaccess" ] ; then chown www-data .htaccess chgrp www-data .htaccess chmod 664 .htaccess fi chmod 777 *.sh chown root chdir.sh chgrp root chdir.sh chmod 770 chdir.sh exit [save and exit] chmod 777 chdir.sh After placing WordPress files, in this example into /var/www/html: ./chdir.sh I have /var/www/html as chmod 2775, chdir www-data and chgrp www-data, and /var/www as chmod 2775
Make sure your installation directory no longer has any test index.html file. We want PHP to run by default.
Then you can run the WP install script with https://my_domain.com
If the installation tries to use FTP, your permissions are incorrect. Check /var/www/html is www-data group and owner, and 2775 permissions. www can be root, but check it is 2775.
I prefer using the same admin user and password as the database itself.
Then test your WordPress installation can login and works.
If you add the Wordfence plugin and have issues, remember it is installing hidden files in /var/www/html and files in /var/www/html/wp-content/ and /var/www/html/wp-content/plugins. There will be a stanza in wp-config.php which may be referencing an incorrect PHP version. I once had to add these lines:
<IfModule mod_php8.c> php_value auto_prepend_file '/var/www/html/wordfence-waf.php' </IfModule>
I doubt you will, but you can see how configurations sometimes need problem solving and how code can change over time from the developers.
At the end of your wp-config.php file, you can add these lines for your memory, ability to upload any type of file to the media library, and use of the SMTP email plugin so that emails can be routed through Amazon. If using MS Exchange, as an example, you would use different settings.
define('WP_MEMORY_LIMIT', '512M'); define('DISALLOW_FILE_EDIT', true); define( 'ALLOW_UNFILTERED_UPLOADS', true ); define('DISABLE_WP_CRON', true); define( 'WPMS_ON', true ); define( 'WPMS_SMTP_PASS', 'YOUR AMAZON IAM SMTP PRIVATE KEY' );
Here is an example of the SMTP plugin using MS Exchange as the router. All this depends on DNS records being previously setup correctly.
WP Mail SMTP Plugin - example of MS Exchange From Email: contact@laurenceshaw.au Force From Email: ON From Name: Laurence Shaw Force From Name: ON Return Path: ON Mailer: Other SMTP SMTP Host: smtp-mail.outlook.com Encryption: TLS SMTP Port: 587 Authentication: ON SMTP Username: contact@laurenceshaw.au SMTP Password: THIS GOES INTO THE wp-config.php FILE
Here is an example of Amazon Oregon as the router:
WP Mail SMTP Plugin - example of Amazon AWS SES router, Oregon From Email: contact@laurenceshaw.au Force From Email: ON From Name: Laurence Shaw Force From Name: ON Return Path: ON Mailer: Other SMTP SMTP Host: email-smtp.us-west-2.amazonaws.com Encryption: TLS SMTP Port: 587 Authentication: ON SMTP Username: AKIA................ (YOur own IAM SES email keys) SMTP Password: THIS GOES INTO THE wp-config.php FILE