This article shows the Linux 2023 packages we install & configure as used by our WordPress servers.


Linux 2023 Packages


Installation and Configuration details are always changing. These are okay at time of writing.

 

We use more than a vanilla installation for WordPress. Our packages include:
Nginx 1.29 or above
Maria DB v11
phpMyAdmin (too manage the databases)
php, and php-fpm used to process php from Nginx
memcached
opcache (we never recommend apcu)
php-* packages
network and other various utilities such as jp, dig, whereis, ipcalc, dos2unix
ImageMagick
nftables
The AWS-SDK – to send email alerts instead of using Postfix, and access to s3 buckets if the same is programmed in shell scripts such as making file/data backups to buckets.
Optionally the package for use of EC2 EFS disk – handy if you need some temporary extra space
We are able to install NFS software for mounting an S3 bucket but prefer not to
We do not install AWS credentials any more in ~/.aws, and although we can use a credential in the WP MAIL SMTP plugin, we prefer not to.

This article is based on a t4g.micro ARM architecture Linux 2023 instance.
Please see the article on disk swap space first.

We will go forward on the basis that Amazon AWS resources will be accessed via an attached EC2 IAM Role, with Linux access via the aws-sdk package using php or bash shell scripts.

Your EC2 Instance Security Group

Your EC2 Security Group(s) will need these settings. You attach these to the Instance. These can be modified.

 

HTTP  TCP 80  ::/0
HTTP  TCP 80  0.0.0.0/0
HTTPS TCP 443 ::/0
HTTPS TCP 443 0.0.0.0/0
Custom TCP TCP 11211 127.0.0.0/16 --> for memcached
SSH TCP 22 YOUR_IP --> e.g. your broadband provider's ip address, limiting who can access SSH. Do not leave open to the public as this will flood the resources.
NFS TCP 2049 0.0.0.0/0 --> if mounting an EFS disk

 

Initial Steps

See our article on Disk Swap Space – required before adding packages.

Add your timezone: (e.g. Brisbane)

a="Australia/Brisbane";export a;echo $a
ln -sf /usr/share/zoneinfo/$a /etc/localtime
date

Modify /etc/bashrc to show your domain name as used with the terminal SSH prompt.

Replace MYDOMAIN with your own name - any name is fine]
vi /etc/bashrc
# [ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@\h \W]\\$ "
  [ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@MYDOMAIN: \w]\\$ "
[save and exit]

Log out, and back in to see the change.

Fix SELINUX:

vi /etc/selinux/config
# SELINUX=permissive
SELINUX=disabled
[save and exit]

We now update the system to a current level of the OS:

dnf update
dnf check-release-update

--> For example: dnf upgrade --releasever=2023.7.20250512 - a long time on a nano instance if there are many prior months of updates having occured.

journalctl and dmesg system files

It is helpful to review any memory errors using “dmesg -T” or “journalctl -e”.

The journal can become too huge to work with. You can configure it to empty every 3 days, as an example.

To do this, uncomment the following line with the value you’d like:

vi /etc/systemd/journald.conf
MaxRetentionSec=3d

You could follow this manually with: “journalctl –vacuum-time=3d”

You cannot use scripts to clean these logs.

To empty the dmesg messages, “dmesg -C”.

dmesg -T shows any errors with the date and time.

--> at the top of the file:

cd /etc/ssh

vi ssh_config

ClientAliveInterval 120
ClientAliveCountMax 360

[save and exit]

vi sshd_config

#PermitRootLogin prohibit-password
PermitRootLogin yes
ClientAliveInterval 120
ClientAliveCountMax 360
#ClientAliveInterval 0
#ClientAliveCountMax 3

[save and exit]

systemctl daemon-reload
systemctl reload sshd
systemctl restart sshd

--> 
If you get this error: (unlikely)
[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
the fiix this as follows:
cd /var/lib/sss/db
rm *
sss_cache -E
<--

 

Linux 2023 dnf Packages

At time of writing, we use php8.4. If you need an earlier version for software compatibility, use 8.2 in the lines below.

dnf search php

--> This will show the available versions.

dnf install php8.4

php -v

PHP 8.4.14 (cli) (built: Oct 21 2025 19:23:55) (NTS gcc aarch64)
Copyright (c) The PHP Group
Built by Amazon Linux
Zend Engine v4.4.14, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.14, Copyright (c), by Zend Technologies

--> 
If installing an earlier version, it is still possible to knock it out and find php is at the current version. You would need to use AI and internet articles to get back to your older version.
I personally do not like upgrading php levels, e.g. from 8.2 to 8.4, but it can be done. You need to ensure you have not wamring messages hanging around.
I also do not like upgrading Debian OS, e.g. from Debian 11 to Debian 13, but again, it can be done.
<--

From this point on, installation will usually vary depending on how developers change the packages.

You need problem solving and research skills. Don’tt worry if it seems a bit messy. If things completely fail, (unlikely) you can delete the instance and start again.

NEVER ACCEPT AN INSTANCE THAT IS INITIALLY PERFORMING SLOWLY WHEN INSTALLING PACKAGES

An instance is basically a hard disk volume – you can check it is 100% available in the EC2 Console’s Volumes menu.

If it is 100% available but is too slow while working with it, delete it and start a new instance. We do experience hardware and network failures at times. Also check the IP address is not blacklisted (see mxtoolbox.com).

After you have installed your domain and DNS entries, you can check if there is a health issue with domain name resolving using this command:

getent hosts mydomain.com  – use your own domain name
xxx.xxx.xxx.xxx mydomain.com – should be listed with your instance public IP address.

Sometimes we see error messages showing us packages we need to install first. Some messages should not be acted on, for instance, php.ini should not need .so files added to it. It is okay to use the isntall command on previously installed packages, or to delete and install again, or to “reinstall”.


dnf install -y php-pear

--> we will assume for EC2 Linux 2023, no need to specify php8.4-SOMEPACKAGE, that it knows how to do so with php-SMEPACKAGE instead.

dnf install -y php-mysqli php-mbstring php-cli php-pdo php-fpm php-json php-mysqlnd php-opcache 
dnf install -y gd libzip-devel kernel-devel php-gd
dnf install -y cronie cronie-anacron
dnf -y install pcre-devel gcc zlib zlib-devel
dnf update

dnf search mariadb

--> At time of writing, we use mariadb1011 - some differences to prior db commands we may have been used to using in shell scripts.

dnf install -y mariadb1011
dnf install -y dnf install -y mariadb1011-server

--> keep in mind there will be dfferences to a Debian installation. Debian would need php8.4-cli instead of php-cli as an example, if wanting to use 8.4.

dnf -y install libjpeg-turbo-utils php-devel php-zip


pecl install zip
pecl channel-update pecl.php.net

--> 
Don't worry about error messages.
We do not add .so entries into php.ini for extensions, but in this case we do. We have .so calls in config files in /etc/php.d instead - check there are no duplicates later when running systemctl status commands.
We do not need to add extension=zip.so; to php.ini
<--

--> Install certbot for Let's Encrypt SSL

dnf -y install python3 python3-devel augeas-libs

python3 -m venv /opt/certbot/
/opt/certbot/bin/pip install --upgrade pip
/opt/certbot/bin/pip install certbot 

dnf -y install letsencrypt

whereis certbot
certbot: /usr/bin/certbot /opt/certbot/bin/certbot

--> If we see a warning about Python v3.9 being sunset, it is not our concern, but that of the developers.

We are not configuring just yet, only installing packages.

We now install memcached – this is surely going to change over time, but here it is for now: (we must use memcached with nginx)

cd /home/ec2-user

--> There are two entries below for php8.4. You must edit for your own php version if different. Ignore error messages.


dnf -y install php8.4-devel php-pear gcc && \
   pear update channels && \
   pecl update channels && \
   \
   # build + install igbinary for use by php-memcached
   
   pecl install igbinary && \
   echo "extension=igbinary.so" | sudo tee /etc/php.d/20-igbinary.ini && \
   \
   # build + install msgpack for use by php-memcached
   pecl install msgpack && \
   echo "extension=msgpack.so" | sudo tee /etc/php.d/20-msgpack.ini && \
   \
   # build + install php-memcached
   dnf install -q -y memcached-devel memcached \
       libmemcached-awesome-devel libmemcached-awesome \
       zlib-devel zlib cyrus-sasl cyrus-sasl-devel \
       libevent libevent-devel && \
   pecl install --configureoptions \
      'with-zlib-dir="no" \
       with-system-fastlz="no" \
       with-libmemcached-dir="no" \
       enable-memcached-igbinary="yes" \
       enable-memcached-msgpack="yes" \
       enable-memcached-json="yes" \
       enable-memcached-protocol="yes" \
       enable-memcached-sasl="yes" \
       enable-memcached-session="yes"' memcached &&  \
   echo "extension=memcached.so" | sudo tee /etc/php.d/25-memcached.ini && \
   \
   # clean up php dev tools and the dnf cache
   dnf remove -y gcc php8.4-devel php-pear libzip-devel \
      memcached-devel libmemcached-awesome-devel zlib-devel \
      cyrus-sasl-devel libevent-devel && \
   dnf autoremove -y && dnf clean all && rm -rf /var/cache/dnf

--> Then run:

pear update-channels
pecl update-channels

--> Now run these commands to fix errors:

dnf install -q -y memcached-devel libmemcached-awesome-devel zlib-devel cyrus-sasl-devel libevent-devel
/usr/bin/yes 'no' | pecl install --configureoptions 'enable-memcached-igbinary="yes" enable-memcached-msgpack="yes" enable-memcached-json="yes" enable-memcached-protocol="yes" enable-memcached-sasl="yes" enable-memcached-session="yes"' memcached

--> Run this command to add the .so file to php if it is not already listed in /etc/php.d

echo 'extension=memcached.so' > /etc/php.d/41-memcached.ini


--> Verify memcached is present:

cd /
find . -name memcached.so -print

--> You will see:

./usr/lib64/php8.4/modules/memcached.so

--> enable memcached

systemctl enable memcached

Install additional packages:

cd /home/ec2-user

dnf install -y nftables
dnf install -y nftables-devel
dnf install -y jp

dnf install dnsutils -y
dnf install ipcalc -y
dnf install dos2unix -y
dnf install whois -y

--> For use of aws-sdk:

dnf install composer -y

--> Verify installation
composer --version

--> after nginx is installed, you want /var/www to be chmod 2775; chown nginx:nginx, and then install the aws-sdk:

cd /var/www
mkdir aws-sdk
cd aws-sdk
composer require aws/aws-sdk-php

--> This will create a vendor/ directory with the SDK and an autoload.php file.
Composer will handle all dependencies automatically.


--> the aws-sdk package can be accessed from other directories using soft links, e.g.:
cd /var/www/html
ln -s /var/www/aws-sdk aws-sdk
ls -l


--> for graphics

dnf update -y
dnf install -y ImageMagick ImageMagick-devel
dnf install -y php-devel php-pear gcc
pecl install imagick

--> check /etc/php.d has a file with extension=imagick.so in it, and if not:
sudo tee /etc/php.d/30-imagick.ini > /dev/null <<EOF
; Enable the ImageMagick extension module
extension=imagick.so
EOF

dnf install php-igbinary -y
dnf install php-msgpack -y

systemctl restart php-fpm
systemctl enable php-fpm

php -m | grep imagick
php -v

Please reboot your server from the EC2 Console

After installing so many packages, reboot the server and check everything is fine with:

php -v

dnf update

It is best to reboot again after installing and configuring mariadb, php configs, phpMyAdmin, opcache, and Nginx.

mariadb, opcache, memcached, php configurations

We usually install a package, and then configure it.

This is the first step for mariadb:

systemctl start mariadb

-->
We are still using mysql_ but this may change to a slightly different command set later.
*** Important Note***  some installations now require "mariadb-secure-installation" and use of the work mariadb instead of mysql such as mysqlcheck
<--

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]

--> Note that we now start and enable all our services. If httpd is not loading SSL correctly, you need to problem solve.

systemctl stop mariadb
systemctl start mariadb
systemctl enable mariadb

--> you will see:
Created symlink /etc/systemd/system/mysql.service → /usr/lib/systemd/system/mariadb.service.
Created symlink /etc/systemd/system/mysqld.service → /usr/lib/systemd/system/mariadb.service.
Created symlink /etc/systemd/system/multi-user.target.wants/mariadb.service → /usr/lib/systemd/system/mariadb.service.

These are recommended configurations for a t4g.micro instance:

cd /etc

cp -p my.cnf my.cnf.bak

vi my.cnf

[mysqld]
# --- Core memory ---
innodb_buffer_pool_size = 256M
innodb_buffer_pool_instances = 1
aria-pagecache-buffer-size = 32M
# --- Disable legacy query cache (saves RAM) ---
query_cache_type = 0
query_cache_size = 0
# --- Connection & thread control ---
max_connections = 20
thread_cache_size = 20
# --- Per-connection buffers (keep small!) ---
tmp_table_size = 16M
max_heap_table_size = 16M
sort_buffer_size = 1M
join_buffer_size = 512K
read_buffer_size = 128K
read_rnd_buffer_size = 256K
# --- Table & index ---
table_open_cache = 200
key_buffer_size = 8M
# --- InnoDB safety & IO ---
innodb_log_file_size = 64M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 2
innodb_file_per_table = 1
innodb_flush_method = O_DIRECT
innodb_flush_neighbors = 1
innodb_io_capacity = 200
# --- Disable binary logging ---
skip-log-bin
# --- Logging ---
log_error=/var/log/mariadb-error.log
log_warnings=9
[client-server]
!includedir /etc/my.cnf.d

[save and exit]

Let’s add OOM settings for mariadb:

--> likely the nano editor. Go to the space below the first comments and add the lines below. To save, type CTRL-O, ENTER, CRTL-X to exit

systemctl edit mariadb

[Service]
Restart=on-failure
OOMScoreAdjust=-1000
RestartSec=5s
Environment="MYSQLD_OPTS="
Environment="_WSREP_NEW_CLUSTER="

[save and exit]

systemctl daemon-reload
systemctl restart mariadb
cat /etc/systemd/system/mariadb.service.d/override.conf

--> check the error log is okay: /var/log/mariadb/mariadb.log

 

We need “systemctl restart mariadb” to have changes put into effect.

Check the status is ok:

systemctl status -l mariadb
● mariadb.service - MariaDB 10.11 database server
     Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; preset: disabled)
    Drop-In: /etc/systemd/system/mariadb.service.d
             └─override.conf
     Active: active (running) since Sun 2026-01-25 15:52:00 AEST; 16h ago
       Docs: man:mariadbd(8)
             https://mariadb.com/kb/en/library/systemd/
   Main PID: 84144 (mariadbd)
     Status: "Taking your SQL requests now..."
      Tasks: 8 (limit: 999)
     Memory: 54.7M
        CPU: 6.689s
     CGroup: /system.slice/mariadb.service
             └─84144 /usr/libexec/mariadbd --basedir=/usr
Jan 25 15:51:59 ip-172-31-34-206.ap-southeast-2.compute.internal systemd[1]: Starting mariadb.service - MariaDB 10.11 database server...
Jan 25 15:51:59 ip-172-31-34-206.ap-southeast-2.compute.internal mariadb-prepare-db-dir[84109]: Database MariaDB is probably initialized in /var/lib/mysql already, nothing is done.
Jan 25 15:51:59 ip-172-31-34-206.ap-southeast-2.compute.internal mariadb-prepare-db-dir[84109]: If this is not the case, make sure the /var/lib/mysql is empty before running mariadb-prepare-db-dir.
Jan 25 15:52:00 ip-172-31-34-206.ap-southeast-2.compute.internal systemd[1]: Started mariadb.service - MariaDB 10.11 database server.

 

We need memcached (not memcache) for Nginx configurations and WordPress plugins that may work better as well.

Your EC2 Securtity Group needs to open the memcached port. Use these settings:

Custom TCP TCP 11211 127.0.0.0/16 memcached

 

memcached

--> this may change in the future if dnf install memcached is available. However: (use your correct php version below, such as 8.4) Ignore error messages.

cd /home/ec2-user

dnf -y install php8.4-devel php-pear gcc && \
   pear update channels && \
   pecl update channels && \
   \
   # build + install igbinary for use by php-memcached
   
   pecl install igbinary && \
   echo "extension=igbinary.so" | sudo tee /etc/php.d/20-igbinary.ini && \
   \
   # build + install msgpack for use by php-memcached
   pecl install msgpack && \
   echo "extension=msgpack.so" | sudo tee /etc/php.d/20-msgpack.ini && \
   \
   # build + install php-memcached
   dnf install -q -y memcached-devel memcached \
       libmemcached-awesome-devel libmemcached-awesome \
       zlib-devel zlib cyrus-sasl cyrus-sasl-devel \
       libevent libevent-devel && \
   pecl install --configureoptions \
      'with-zlib-dir="no" \
       with-system-fastlz="no" \
       with-libmemcached-dir="no" \
       enable-memcached-igbinary="yes" \
       enable-memcached-msgpack="yes" \
       enable-memcached-json="yes" \
       enable-memcached-protocol="yes" \
       enable-memcached-sasl="yes" \
       enable-memcached-session="yes"' memcached &&  \
   echo "extension=memcached.so" | sudo tee /etc/php.d/25-memcached.ini && \
   \
   # clean up php dev tools and the dnf cache
   dnf remove -y gcc php8.4-devel php-pear libzip-devel \
      memcached-devel libmemcached-awesome-devel zlib-devel \
      cyrus-sasl-devel libevent-devel && \
   dnf autoremove -y && dnf clean all && rm -rf /var/cache/dnf

--> Then run:

pear update-channels
pecl update-channels

--> Now run these commands to fix errors:

dnf install -q -y memcached-devel libmemcached-awesome-devel zlib-devel cyrus-sasl-devel libevent-devel
/usr/bin/yes 'no' | pecl install --configureoptions 'enable-memcached-igbinary="yes" enable-memcached-msgpack="yes" enable-memcached-json="yes" enable-memcached-protocol="yes" enable-memcached-sasl="yes" enable-memcached-session="yes"' memcached

--> Run this command to add the .so file to php ONLY if you do not see it in /etc/php.d .ini files

echo 'extension=memcached.so' > /etc/php.d/41-memcached.ini

--> Verify memcached is present:

cd /
find . -name memcached.so -print

--> You will see:

./usr/lib64/php8.4/modules/memcached.so

systemctl enable memcached

memcached configuration:

cd /etc/sysconfig

vi memcached

  PORT="11211"
  USER="memcached"
  MAXCONN="1024"
  CACHESIZE="64"
  OPTIONS="-l 127.0.0.1 -U 0,::1"

[save and exit]

systemctl restart memcached
ps -ef|grep memcached

opcache configuration:

cd /etc/php.d
ls

vi 10-opcache.ini

zend_extension=opcache
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
realpath_cache_size = 4096K
realpath_cache_ttl = 600
opcache.blacklist_filename=/etc/php.d/opcache*.blacklist
opcache.huge_code_pages=0

[save and exit]

 

These are my recommended php.ini configurations.

If the https strict use is an issue, omit it. (We are not behind a proxy service like Cloudflare’s option.)

--> I prefer 256M as the PHP memory. Complex web pages may need more - typically the WordPress editing suddenly gives a blank page with an error message. A small instance has memory demands, fo 512M can cause issues.

cd /etc

cp -p php.ini php.ini.bak

vi php.ini

date.timezone = Australia/Brisbane
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Strict"
session.sid_length = 48
session.sid_bits_per_character = 6
session.use_strict_mode = 1
max_execution_time = 60
max_input_time = 60
max_input_vars = 2800
memory_limit = 256M 
post_max_size = 128M
upload_max_filesize = 128M
; session.save_handler = files
session.save_handler = memcached
session.save_path = "127.0.0.1:11211"

[save and exit]

There are several security configurations above. Check these are already configured:

session.use_trans_sid = 0

session.use_strict_mode = 1

We make sure nginx sees the enforced use of https from the above configuration, with “fastcgi_param HTTPS on;” (refer to the nginx.conf file)

Configure /etc/php-fpm.d/www.conf. This applies to an “on demand” php server with small resources on a t4.micro server.

--> we will change pm = dynamic to ondemand and comment out a couple of lines as a result.
--> note the value for listen = /run/php-fpm/www.sock -- we use this in nginx.conf - debisn uses a different socket.

cd /etc/php-fpm.d

cp -p www.conf www.conf.bak

vi www.conf

user = nginx
group = nginx
; listen.owner = apache
; listen.group = apache
; listen.mode = 0660
listen.acl_users = nginx
pm = ondemand
pm.max_children = 6
; pm.start_servers = 5
; pm.min_spare_servers = 5
; pm.max_spare_servers = 35
pm.process_idle_timeout = 10s;
pm.max_requests = 300
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 256M
php_admin_value[disable_functions] = exec,passthru,system
php_admin_flag[allow_url_fopen] = off
php_value[session.save_handler] = memcached
php_value[session.save_path]    = 127.0.0.1:11211
php_value[soap.wsdl_cache_dir]  = /var/lib/php/wsdlcache

[save and exit]

cd ..

--> place lines anywhere around the emergency lines in the file. Lets us refresh php without errors, and used in crontab.
--> Do not duplicate the process_control_timeout entry.

vi php-fpm.conf

emergency_restart_threshold = 10
emergency_restart_interval = 1m
process_control_timeout = 60s

[save and exit]

systemctl restart php-fpm

--> check it is enabled:

systemctl enable php-fpm

systemctl status -l php-fpm
● php-fpm.service - The PHP FastCGI Process Manager
     Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; enabled; preset: disabled)
     Active: active (running) since Mon 2026-01-26 08:35:02 AEST; 2s ago
   Main PID: 128828 (php-fpm)
     Status: "Ready to handle connections"
      Tasks: 1 (limit: 999)
     Memory: 10.6M
        CPU: 38ms
     CGroup: /system.slice/php-fpm.service
             └─128828 "php-fpm: master process (/etc/php-fpm.conf)"
Jan 26 08:35:02 ip-172-31-34-206.ap-southeast-2.compute.internal systemd[1]: Starting php-fpm.service - The PHP FastCGI Process Manager...
Jan 26 08:35:02 ip-172-31-34-206.ap-southeast-2.compute.internal systemd[1]: Started php-fpm.service - The PHP FastCGI Process Manager.

We now add the OOM restart settings if memory crashes (can be seen in error logs like:

dmesg -T (use -C to clear the logs)

jounrnalctl -e (use another option for the archived logs)

/var/log/php-fpm/error.log, and mariadb’s log

systemctl edit php-fpm.service

[Service]
MemoryMax=975M
MemoryHigh=950M

[save and exit - either nano or vi depending on the OS}

--> fyi: systemctl edit php8.4-fpm.service would be used for Debian php8.4
--> After editing, reload php:

systemctl daemon-reload
systemctl restart php-fpm
systemctl status -l php-fpm

If we had an instance with 2GB RAM, we could use 1500M as a larger value.

When we upgrade the packages, some PHP entries can be lost. This should protect them:

vi /etc/tmpfiles.d/php-fpm-nginx.conf
# /etc/tmpfiles.d/php-fpm-nginx.conf
d /var/lib/php/session 0770 nginx nginx -
d /var/lib/php/opcache 0770 nginx nginx -
d /var/lib/php/wsdlcache 0770 nginx nginx -
[save and exit]
systemd-tmpfiles --create

If we wish to dedicate a www.conf pool to editing WordPress, I have shown in nginx.conf (or other domain include files) how you can have another php pool, with an example where we copy www.conf to admin.conf, and rename the socket to something like admin.sock in this file. Then in nginx.conf we allocate the pool to WordPress.

We cannot have overly large values for our configurations or we will freeze. WIth larger values you see how much snappier things can be, but they will crash.

Use AI to compare our configurations for a larger EC2 instance, and adjust.

phpMyAdmin package and configurations

 

CHANGE NOTES

We can now install with dnf install phpmyadmin but there are some differences. See further below.

There are two ways to install phpMyAdmin.

If the dnf package is available you can use dnf -y phpmyadmin.

I have always used the classic method:

cd /usr/share

wget https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.tar.gz
ls
--> untar the file your downloaded:

tar xvf .....  
--> e.g.: tar xvf phpMyAdmin-latest-all-languages.tar.gz
      rm phpMyAdmin-latest-all-languages.tar.gz
      mv phpMyAdmin-5.2.3-all-languages phpMyAdmin
(Use the ls command to get the correct file names)
<--

--> Then delete the tar.gz file, then use the Unix command to move the new directory to phpMyAdmin, e.g.: mv untared_file phpMyAdmin

cd phpMyAdmin
mkdir tmp
chmod 777 tmp
cp -p config.sample.inc.php config.inc.php
vi config.inc.php

--> After SaveDir, add TempDir, and the permissions line (DisabledIS line) if later you find you cant edit users or export a database
$cfg['SaveDir'] = '';
$cfg['TempDir'] = '/tmp';
$cfg['Servers'][$i]['DisableIS'] = true;

--> We need to edit the blowfish line.
php -r "echo bin2hex(random_bytes(32));"
--> Then paste this into the blowfish double quotes. It only needs to be a random string at least 32 characters in length. I never use ! characters.

We need to fix permissions from apache to nginx:

--> these may change depending on the OS and installation method

cd /var/lib/php
ls -l
chgrp nginx opcache
chgrp nginx session
chgrp nginx wsdlcache
ls -l

We should not need entries in nginx.conf, but we can add .htpasswd as a second login method if we wish.

We want memcached set in both www.conf and php.ini, and that /etc/php.d has the .ini file with extension=memcached.so in it, and that extension=memcached.so does actually exist.

Here are the changes we need for the dnf method:

-->
Installing the phpmyadmin package has some differences from the older wget method.
We have two directories to review:
cd /etc
ls -l

/etc/phpmyadmin

--> change -rw-r----- 1 root www-data  529 Apr  6 11:56 config-db.php to nginx group: 

chgrp nginx config-db.php

cd /var/lib/phpmyadmin

/var/lib/phpmyadmin]# ls -l
total 8
-rw-r----- 1 root     www-data   68 Apr  6 11:56 blowfish_secret.inc.php
drwxr-xr-x 2 www-data www-data 4096 Sep  4  2025 tmp

--> Change www-data to nginx (note: previously we did "apt remove *apache2*")

chown nginx:nginx tmp
chgrp nginx blowfish_secret.inc.php

--> Create the blowfish string:
php -r 'echo substr(str_shuffle("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), 0, 32), PHP_EOL;'

--> Add it to the file blowfish_secret.inc.php (remove the existing string)

 

Install NGINX

Please see our separate article on configuring Nginx. Here we only show how to add the package.

If you use “dnf search nginx”you should be able to see the default package level.

If it is not 1.29 or higher, we use this manual scripting:

--> Install the current Stable version of Nginx:

dnf install -y yum-utils

--> In the vi editing below, we must include the lines that have [....] in them.
--> See: https://nginx.org/en/linux_packages.html#Amazon-Linux

vi /etc/yum.repos.d/nginx.repo

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/amzn/2023/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
priority=9
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/amzn/2023/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
priority=9

[save and exit]

yum-config-manager --enable nginx-mainline

--> Now install nginx, and use the Enter key when prompted a few times...

dnf install -y nginx

--> nginx -t should prove it is installed:

nginx -t

--> Nginx can try to start too quickly. See: https://serverfault.com/questions/1042526/open-run-nginx-pid-failed-13-permission-denied. The fix is:

mkdir -p /etc/systemd/system/nginx.service.d

vi /etc/systemd/system/nginx.service.d/override.conf

[Service]
ExecStartPost=/bin/sleep 0.1

[save and exit]

systemctl daemon-reload
systemctl restart nginx
systemctl enable nginx

--> Check no error:

systemctl status -l nginx

--> This is the sort of thing you will see:

● nginx.service - nginx - high performance web server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: disabled)
    Drop-In: /etc/systemd/system/nginx.service.d
             └─override.conf
             /usr/lib/systemd/system/nginx.service.d
             └─php-fpm.conf
     Active: active (running) since Sat 2026-01-24 14:31:59 AEST; 24s ago
       Docs: http://nginx.org/en/docs/
   Main PID: 16324 (nginx)
      Tasks: 3 (limit: 999)
     Memory: 3.0M
        CPU: 9ms
     CGroup: /system.slice/nginx.service
             ├─16324 "nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf"
             ├─16325 "nginx: worker process"
             └─16326 "nginx: worker process"
Jan 24 14:31:59 ip-172-31-34-206.ap-southeast-2.compute.internal systemd[1]: Starting nginx.service - nginx - high performance web server...
Jan 24 14:31:59 ip-172-31-34-206.ap-southeast-2.compute.internal systemd[1]: Started nginx.service - nginx - high performance web server.

--> When we configure Nginx, like mariadb, there can be messages that seem like a warning that are actually fine.
--> If "php -v" gives a warning, or the log file (/var/log/php-fpm/error.log) you should really aim to fix those.

After nginx is installed, make sure you have /var/www/html where www and html are chmod 2775, and nginx;nginx ownership.

When the webstie is running, you can place a phpinfo.php file into your root, e.g. /var/www/html and check the PHP configuration shows your php.ini changes, opcache, and session.save_handler using memcached:

cd /var/www/html

vi info.php

<?php phpinfo();?>

[save and exit]

chmod 664 i*
chown nginx:nginx i*

https://mydomain.com/info.php

mv info.php info.pgp.bak

Make sure your services are enabled so they kick in on a reboot:

systemctl enable nginx
systemctl enable php-fpm
systemctl enable mariadb
systemctl enable memcached

 

 

 

Helpful shell scripts

Restart the services:

cd /home/ec2-user
vi restart.sh
#!/bin/sh
free -m
sudo systemctl stop nginx
sudo systemctl stop memcached
sudo systemctl stop mariadb
sudo systemctl stop php-fpm
sleep 0.5
sudo systemctl start php-fpm
sudo systemctl start mariadb
sudo systemctl start memcached
sudo systemctl start nginx
sudo swapoff -a
sleep 1
sudo swapon -a
sudo systemctl status -l nginx
sudo systemctl status -l memcached
sudo systemctl status -l mariadb
sudo systemctl status -l php-fpm
free -m
df -hT
exit
[save and exit]
chmod 777 restart.sh; chown root:ec2-user restart.sh
--> test it out:
./restart.sh

Reload the services (does not interrupt transactions and only refreshes swap space if able)

cd /home/ec2-user
vi reload.sh
#!/bin/sh
free -m
sudo /usr/bin/mariadb-admin refresh >/dev/null 2>&1
sudo /usr/bin/systemctl start mariadb >/dev/null 2>&1
m=""
m=`ps -ef|grep php|grep master`
if [ "$m" != "" ] ;
then
okay=""
else
sudo /usr/bin/systemctl start php-fpm
fi
sudo /usr/bin/systemctl reload php-fpm >/dev/null 2>&1
sudo /usr/bin/systemctl start php-fpm >/dev/null 2>&1
sudo /usr/bin/systemctl restart memcached >/dev/null 2>&1
sudo /usr/sbin/nginx -s reload >/dev/null 2>&1
# in case nginx was down
sudo /usr/bin/systemctl start nginx >/dev/null 2>&1
mem=$(LC_ALL=C free  | awk '/Mem:/ {print $4}')
swap=$(LC_ALL=C free | awk '/Swap:/ {print $3}')
echo RAM memory: $mem
echo Disk swap : $swap
if [ $mem -lt $swap ] ;
then
    echo "ERROR: not enough RAM to write swap back, so nothing done" >&2
    exit 1
else
swapoff -a &&
sleep 1
swapon -a
fi
free -m
exit
[save and exit]
chmod 777 reload.sh; chown root:ec2-user reload.sh
--> test it:
./reload.sh

Use the “free -m” command to review the results.

WordPress WP API, WP Crontab, Image Compression, Defender, W3TC

REVIEW THIS AFTER NGINX and WORDPRESS IS INSTALLED AND WORKING FOR YOUR DOMAIN

If intending to use the WordPress api (wp commands):

cd /home/ec2-user
curl -L -o wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
wp --info
ls /usr/local/bin/wp

If you add this line to wp-config.php in WordPress:

define( ‘DISABLE_WP_CRON’, true );

then you can run cron jobs from crontab with entries like this: (note, this may not work with some themes and plugins)

-> Use your own domains and paths.
# WordPress cron via WP-CLI (staggered)
*/15 * * * * flock -n /tmp/wpcron_site1.lock /usr/bin/php /usr/local/bin/wp cron event run --due-now --path=/var/www/html > /dev/null 2>&1
*/15 * * * * sleep 120 && flock -n /tmp/wpcron_site2.lock /usr/bin/php /usr/local/bin/wp cron event run --due-now --path=/var/www/MYNEXTDOMAIN.COM > /dev/null 2>&1
--> You can manually test, e.g. /usr/bin/php /usr/local/bin/wp cron event run --due-now --path=/var/www/html

You can check these commands manually to see if any issues after your WordPress installation is reasonably complete.

You may not necessarily need an image optimiser and file size plugin.

You can restrict image sizes by placing this php script in the ./wp-content/mu-plugins directory (create the directory if not there). It also makes sure you can upload font files to the media library.

--> This is best to return to after nginx and WordPress is installed

cd ./wp-content/mu-plugins

vi image-sizes.php

<?php
/**
 * Limit generated image sizes to standard ones.
 */
add_filter( 'intermediate_image_sizes_advanced', function( $sizes ) {
    // Keep only these standard sizes
    $allowed = [ 'thumbnail', 'medium', 'medium_large', 'large' ]; 
    foreach ( $sizes as $size => $dimensions ) {
        if ( ! in_array( $size, $allowed, true ) ) {
            unset( $sizes[ $size ] );
        }
    }
    return $sizes;
} );
add_filter( 'jpeg_quality', function( $arg ) {
    return 100; // optional: max JPEG quality
} );
add_filter( 'wp_editor_set_quality', function( $quality, $mime_type ) {
    return ( 'image/webp' === $mime_type ) ? 100 : $quality;
}, 10, 2 );
 function allow_font_uploads($mimes) {
     $mimes['ttf'] = 'font/ttf';
     $mimes['otf'] = 'font/otf';
     $mimes['woff'] = 'font/woff';
     $mimes['woff2'] = 'font/woff2';
     $mimes['eot'] = 'font/eot';
     $mimes['svg'] = 'font/svg';
     return $mimes;
 }
 add_filter('upload_mimes', 'allow_font_uploads');

[save and exit]

chmod 664 *
chown nginx:nginx *
ls -l

systemctl restart php-fpm


If using the W3TC caching plugin, do not enable gzip compression as this is a double up (nginx does it) and slows performance.
If using the Defender plugin, database entries get ridiculously long.
Use this approach, or check with AI on how to truncate leaving so many days worth of entries:

 

--> 
Example for two sites. I think you may be able to use /home/ec2-user/.my.cnf as well - not tested.
Use your own AAA/BBB database name values and table names of abc_, def_ - see your tables or what prefix in wp-config.php
<--

cd ~

vi .my.cnf

[client]
user=root
password=xxxxxxxxxxxx
[client_site1]
database=AAA
[client_site2]
database=BBB

[save and exit]

chmod 600 ~/.my.cnf

--> in crontab:

# Clean Defender Plugin ssa_defender_antibot entries 5am every 7 days
0 5 */7 * * /usr/bin/mysql AAA -e "TRUNCATE TABLE abc_defender_antibot;" >/dev/null 2>&1
5 5 */7 * * /usr/bin/mysql BBB -e "TRUNCATE TABLE def_defender_antibot;" >/dev/null 2>&1

-->
Where AAA, BBB are the database names, and abc_ def_ are the wp-config.php table prefixes.
This is a way to preserve the previous 7 days in the tables:
mysql -uDBUSER -pDBPASS DBNAME -e "DELETE FROM wp_defender_antibot WHERE date_time < NOW() - INTERVAL 7 DAY;" 
Don't need to restart anything.
<--

Check all is well with dnf update, then it is a good time to reboot the server and check services are running.

CHANGE NOTES

Note that on Debian 13 we use:
apt -y install python3.13 python3.13-venv
python3 -V
Python 3.13.5

This prevents the certbot warning of phython becoming deprecated