How to install Amazon Linux 2022 AL22 & WordPress
[log in as root: sudo su] 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 600 /swapfile swapon /swapfile echo "/swapfile swap swap defaults 0 0" >> /etc/fstab free -m dnf update dnf upgrade dnf install -y httpd httpd-tools mod_ssl dnf install -y php php-common php-pear wget php-mysqli php-devel php-mbstring dnf install -y php-cli php-pdo php-fpm php-json php-mysqlnd php-opcache dnf install -y gd libzip-devel httpd-devel kernel-devel php-gd postfix [ Notes: - If using shell scripts acessing ImageMagick: dnf install -y ImageMagick ImageMagick-devel There is no php-ImageMagick at time of writing. I no longer use memcached as there is no php-memcahced. If there were, you would update the inbound rules of the EC2 instance security group and dnf install memcahced php-memcached and configure the last line of /etc/sysconfig/memcached. - We need to install ZipArchive (php-zip) for various apps that may be used in WordPress that require zip. We will install this here as well... ] dnf -y install pcre-devel gcc zlib zlib-devel pecl install zip pecl channel-update pecl.php.net [Now edit /etc/php.ini and after the Dynamic section (as shown below) add zip.so. Note that in Linux2 we coud simply do yum install php-zip and that was all we had to do. If we had put zip.so into php.ini there would be an error in /etc/httpd/log/error_log:] vi /etc/php.ini ;;;;;;;;;;;;;;;;;;;;;; ; Dynamic Extensions ; ;;;;;;;;;;;;;;;;;;;;;; extension=zip.so; [Save and exit the editor, then restart httpd.] wget -O epel.rpm –nv \ https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm rpm -ihv --nodeps ./epel-release-latest-8.noarch.rpm dnf install -y cronie cronie-anacron [ Note: The script advises to execute /usr/bin/crb enable but I am not sure why as crontab is working without that. You could do it anyway. /usr/bin/crb enable Enabling CRB repo CRB repo is disabled ] a="Australia/Brisbane";export a;echo $a ln -sf /usr/share/zoneinfo/$a /etc/localtime date [ Change /etc/bashrc to your preferred method of PS1. I use this: (comment out the existing line - exit the shell a little further below.) # [ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@\h \W]\\$ " [ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@mydomain.au: \w]\\$ " ] usermod -a -G apache ec2-user [exit and log back in as root] chown -R ec2-user:apache /var/www chmod 2775 /var/www && find /var/www -type d -exec sudo chmod 2775 {} \; find /var/www -type f -exec sudo chmod 0664 {} \; dnf -y install mariadb105 dnf -y install mariadb105-server [for certbot/let'sencrypt and PIP rather than snapd] dnf install python3 dnf install python3-devel dnf install augeas-libs python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot certbot-apache ln -s /opt/certbot/bin/certbot /usr/bin/certbot dnf check-release-update [repeat these steps till no upgrades are left. e.g. dnf update --releasever=2022.0.20221019] sync;sync;reboot [log back in as root]
Use the vi editor, nano or other on the following files…
/etc/php.ini
date.timezone = Australia/Brisbane max_execution_time = 300 max_input_time = 600 max_input_vars = 2500 post_max_size = 50M upload_max_filesize = 50M max_file_uploads = 20 memory_limit = 256M [You may have other values]
/etc/php.d/10-opcache.ini
opcache.enable_cli=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=4000
/etc/sysconfig/memcached
OPTIONS="-l 127.0.0.1 -U 0,::1"
/etc/selinux/config
# SELINUX=permissive SELINUX=off
/etc/php-fpm.d/www.conf
cp -p www.conf www.conf.bak ; pm = dynamic pm = ondemand pm.max_children = 50 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 35 pm.process_idle_timeout = 10s; pm.max_requests = 500 php_admin_value[memory_limit] = 256M [I realise there is a semicolon at the end of one of the above lines]
/etc/httpd/conf/httpd.conf
(use your own domain name)
[after Listen 80 add:] KeepAlive On MaxKeepAliveRequests 50 KeepAliveTimeout 5 [in the <Directory "/var/www/html"> section:] AllowOverride All [in the <IfModule dir_module> section after it:] DirectoryIndex index.php index.html [fix the ServerName to your domain:] #ServerName www.example.com:80 ServerName mydomain.au [If intending to add IP2Location for country blocking, add these lines at the end of the file:] <IfModule mod_ip2location.c> IP2LocationEnable On IP2LocationDetectProxy On IP2LocationSetmode ALL IP2LocationDBFile /home/ec2-user/ip2location/IP2LOCATION-LITE-DB1.BIN </IfModule>
/etc/httpd/conf.modules.d/00-mpm.conf
[we do not use http/2 on small instances] LoadModule mpm_prefork_module modules/mod_mpm_prefork.so #LoadModule mpm_worker_module modules/mod_mpm_worker.so [append these for performance:] <IfModule mpm_prefork_module> StartServers 2 MinSpareServers 2 MaxSpareServers 5 MaxRequestWorkers 125 ServerLimit 125 MaxConnectionsPerChild 0 </IfModule>
/etc/httpd/conf.modules.d/00-proxy.conf
[typicall heartbeat has caused continuous error log messages] # LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
Add a new file, /etc/httpd/conf.d/phpMyAdmin.conf
Use your own static IP address (otherwise edit it every time you use phpMyAdmin for your dynamic address). Replace xxx.xxx.xxx.xxx with your IP address. This lets you access the phpMyAdmin database GUI.
Note: we are not configuring anything complex with httpd.
Alias /phpMyAdmin /usr/share/phpMyAdmin Alias /phpmyadmin /usr/share/phpMyAdmin <Directory /usr/share/phpMyAdmin/> AddDefaultCharset UTF-8 <IfModule mod_authz_core.c> # Apache 2.4 <RequireAny> Require ip xxx.xxx.xxx.xxx Require ip ::1 </RequireAny> </IfModule> <IfModule !mod_authz_core.c> # Apache 2.2 Order Deny,Allow Deny from All Allow from xxx.xxx.xxx.xxx Allow from ::1 </IfModule> </Directory> <Directory /usr/share/phpMyAdmin/setup/> <IfModule mod_authz_core.c> # Apache 2.4 <RequireAny> Require ip xxx.xxx.xxx.xxx Require ip ::1 </RequireAny> </IfModule> <IfModule !mod_authz_core.c> # Apache 2.2 Order Deny,Allow Deny from All Allow from xxx.xxx.xxx.xxx Allow from ::1 </IfModule> </Directory> <Directory /usr/share/phpMyAdmin/setup/frames/> Order Deny,Allow Deny from All Allow from None </Directory>
/etc/httpd/conf.d/ssl.conf
Note, until we are ready to install SSL and restart httpd, I move the file to ssl.bak. When I am ready I move it to ssl.conf.
Certbot appends lines, so I will show what it looks like at the end of the file. Check the conf.d directory for spurious ssl config files if certbot configs go wrong and delete them. We don’t want multiple domainname-001 type files hanging around by mistake as certbot installs can be messy.
Note: you can run http:// while installing mariadb further below, but have your own checklist t remind you of configs t0 complete. You should not log into phpMyAdmin until SSL is configured.
[at the top of the file (use your own domain name). (I will already have a CNAME record in the DNS configs for www.mydomain.au pointing to mydomain.au.] <VirtualHost *:80> ServerName mydomain.au Redirect permanent / https://mydomain.au/ RewriteEngine on RewriteCond %{SERVER_NAME} =mydomain.au RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent] </VirtualHost> #ServerName www.example.com:443 ServerName mydomain.au:443 SSLEngine on #SSLProtocol all -SSLv3 #SSLProxyProtocol all -SSLv3 SSLProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 SSLProxyProtocol -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +TLSv1.2 # note: the next two long lines must have no carriage return line feeds. Please check to make sure. 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 SSLCipherSuite PROFILE=SYSTEM SSLProxyCipherSuite PROFILE=SYSTEM SSLCompression off SSLInsecureRenegotiation Off SSLSessionTickets Off SSLOpenSSLConfCmd ECDHParameters secp384r1 SSLOpenSSLConfCmd Curves secp384r1 # we comment out the following lines if using certbot, otherwise we change them to our own certificates from Comodo or Sertigo. It is easy to create these files with an editor, but you may be using bundled files as well. # SSLCertificateKeyFile /etc/pki/tls/private/localhost.key #SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt # just before the last line of </VirtualHost>, certbot should add these lines during its configurations, and if not you can and check the .pem files exist. It is no magic as you can tar file the letsencrypt directory and re-install on another instance without problems if you upgrade your EC2 instance using the ame domain name. ServerAlias mydomain.au ServerAlias www.mydomain.au SSLCertificateFile /etc/letsencrypt/live/mydomain.au/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/mydomain.au/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf
You may notice some differences to Linux2.
Configuring Postfix – we use this to send internal emails.
/etc/postfix
Add the following lines to a new file sasl_passwd, using the square brackets as shown and the e-mail region. I use Oregon. There is no e-mail region in Australia.
You will have previously created SMTP credentials from the SES console. Use these where it says SMTPUSERNAME:SMTPPASSWORD below.
cd /etc/postfix vi sasl_passwd [email-smtp.us-west-2.amazonaws.com]:587 SMTPUSERNAME:SMTPPASSWORD
continuing…
systemctl stop postfix;systemctl disable postfix;ps -ef|grep postfix postconf -e "relayhost = [email-smtp.us-west-2.amazonaws.com]:587" \ "smtp_sasl_auth_enable = yes" \ "smtp_sasl_security_options = noanonymous" \ "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd" \ "smtp_use_tls = yes" \ "smtp_tls_security_level = encrypt" \ "smtp_tls_note_starttls_offer = yes" [Enter the above lines with the \ then press RETURN KEY to execute them. Remember, these lines show Oregon as the region. If you use North Virginia you would need that region.] postmap hash:/etc/postfix/sasl_passwd chmod 0600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db postfix start; sudo postfix reload; postfix flush mailq [Now do a test e-mail, then disable postfix for security reasons and only call it from shell scripts] sendmail -f admin@mydomain.au admin@mydomain.au From: admin <admin@mydomain.au> Subject: Postfix Test This is a test message from AWS Postfix and SES . mailq [It should have sent without errors. If not, clean up the /var/log and fix the error. If you are in sandbox mode, use the verified email address you created in SES, and check your DNS records. It could also be you see errors in Cloudwatch logs in Oregon region if you perhaps made mistakes in your Lambda or SES setups. These details are as per a spearate article.] systemctl disable postfix postfix stop
Configure mariadb:
systemctl start mariadb 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 start mariadb systemctl enable mariadb systemctl enable httpd systemctl enable php-fpm systemctl enable memcached [Memcached is optional. You do not have to install memacached.] php -v [This will show version 8.1 or above] [You can check the version of Mariadb with: dnf list|grep mariadb]
Configuring phpMyAdmin…
cd /usr/share wget https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.tar.gz ls tar xvf ..... [where ..... is the downloaded file. Then delete the tar.gz file, then use the Unix command to move the directory to phpMyAdmin, e.g.: mv yourfile 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. Do a Google search on blowfish phpmyadmin generator. I use: https://phpsolved.com/phpmyadmin-blowfish-secret-generator/?g=[insert_php]echo%20$code;[/insert_php] from https://phpsolved.com. Paste the generated value into the blowfish value. Then after SaveDir as shown below, add TEMPDir...] $cfg['SaveDir'] = ''; $cfg['TEMPDir'] = '/tmp'; [Restart httpd - recall we may not have SSL running, so you should not really log into phpMyAdmin at this stage. You can check the interface is ready with http://mydomain.au/phpMyAdmin. As a note, you can view your PHP settings with http://mydomain.au/phpinfo.php. I have a separate article on using phpMyAdmin.]
SSL must be installed. You can reboot your server after it is in place.
Please see my separate articles on IP2location and S3FS (NFS), CDN if you wish to use those capabilities.
DNS records:
[These values must be in your Route53 (or other provider's) CAA records:] 0 issue "letsencrypt.org" 0 issue "amazon.com" 0 issue "amazontrust.com" 0 issue "awstrust.com" 0 issue "amazonaws.com" 0 issuewild ";" If using Comodo, use these: (I have not used Sertigo so cannot give those configs)] 0 issue "comodoca.com" 0 issue "comodo.com" 0 issue "usertrust.com" 0 issue "trust-provider.com" 0 issue "amazon.com" 0 issue "amazontrust.com" 0 issue "awstrust.com" 0 issue "amazonaws.com" 0 issuewild ";"
Install the certificates:
This is fiddly…
I like to start httpd without ssl.conf (move it to ssl.conf.o) then copy the ssl.conf.o file to ssl.conf after httpd starts, so that certbot can access it.
Then make sure ssl.conf has all the correct entries before running certbot for ssl (e.g. configure it for httpd as shown in this article).
Then run the certbot command as shown, (of course without the dry run when it says there is no failure).
Then run:
certbot –apache
Then edit the ssl.conf file to remove the mydomain.au-001 entries and delete them from /etc/letsencrypt/live, and past the entries below into the ssl.conf file. This does work!!
[Use your own domain name. First use the dry run to test, then run it again without the --dry-run option:] /usr/bin/certbot -v certonly -d mydomain.au -d www.mydomain.au --webroot -w /var/www/html --dry-run [Certbot has lots of instructions you can experiment with, but at the end of the day, you want the above dry run to work, and you want to see entries in ssl.conf. e.g.: ServerAlias mydomain,au ServerAlias www.mydomain.au SSLCertificateFile /etc/letsencrypt/live/mydomain.au/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/mydomain.au/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf </VirtualHost> ] [If you have othe ssl files created by certbot in /etc/httpd/conf.d, you can move them so they are not accessed by Apache (httpd) and try again. For isntance, when testing, you possibly may try certbot --apache. If you have multiple attempts, you may have too many certificate files, ending in sequence numbers. Just delete those references and files. All you need to end up with is the above ssl.conf entries, and /etc/letsencrypt/live/mydomain.au, then the only files under that directory are cert.pem chain.pem fullchain.pem privkey.pem README. There is no complication here. Just end up with these values and files.] [I see no need to have the DNS plugins certbot describes. We cannot install the plugin on AL22, but we could on Linux2. To what purpose would we need it?] [Set up crontab scripting to renew the certificates very 60 days. If longer, you get annoying e-mails. Remember to delete certificates and deregister the domain name if you ever decide to remove them. Simply type certbot --help to see the options.] crontab -e 15 1 * * * /home/ec2-user/certbot.sh >/dev/null 2>&1 [save and exit the editor after adding the above line] crontab -l cd /home/ec2-user [use your own domain name below] vi certbot.sh #!/bin/bash d=`date` c1=`head -1 /home/ec2-user/certbot.dat` let c1=$c1+1 if [ "$c1" = "60" ] ; then echo "0" > /home/ec2-user/certbot.dat echo "Certbot Renewal" $d >> /home/ec2-user/info.log # sudo /usr/bin/certbot -v certonly -d mydomain.au -d www.mydomain.au --webroot -w /var/www/html sudo /usr/bin/certbot -v renew --webroot -w /var/www/html sudo /usr/bin/systemctl restart httpd >/dev/null 2>&1 sudo /usr/bin/openssl x509 -noout -dates -in /etc/letsencrypt/live/mydomain.au/cert.pem >> /home/ec2-user/info.log else echo "Certbot day $c1 of 60" >> /home/ec2-user/info.log echo $c1 > /home/ec2-user/certbot.dat fi exit [save and exit the above new file Note: renew option is required for the shell script. I have tested using the DNS plugin ok, but am waiting to confirm the above line in the script will work for the webroot option. 24th Jan 2023] touch info.log [If you just created the certificates, you will use the numeral 0 on the file below, representing the first of the 60 days.] vi certbot.dat 0 [save and exit the editor] chown root *sh *log *dat chgrp ec2-user *sh *log *dat chmod 777 *sh *log *dat [Of course you may use 775 on the shell scripts if you wish]
Test your certificates at https://www.ssllabs.com/ssltest/
You want an A rating, not an A+.
If you want the business name in the SSL certificate, you need to purchase one. The lowest cost certificate is a Positive DV certificate.
If you want multiple subdomains, that is another cost, and requires use of the openssl command to generate them unless the SSL provider has all those setups done for you.
cd /var/www/html vi chdir.sh #!/bin/sh chown -R apache * chgrp -R apache * find . -type d -exec chmod 2775 {} \; find . -type f -exec chmod 0664 {} \; if [ -f "./.htaccess" ] ; then chown apache .htaccess chgrp apache .htaccess chmod 664 .htaccess fi chmod 777 *.sh chown root chdir.sh chgrp root chdir.sh chmod 770 chdir.sh exit [save and exit the editor, then run the script] chmod 775 chdir.sh ./chdir.sh ls -la
RewriteEngine on RewriteCond %{HTTPS} off RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] RewriteCond %{SERVER_PORT} 80 RewriteRule ^(.*)$ https://mydomain.au/$1 [R,L] RewriteCond %{ENV:IP2LOCATION_COUNTRY_SHORT} ^RU$ RewriteRule ^(.*)$ https://google.com.au [L] RewriteCond %{ENV:IP2LOCATION_COUNTRY_SHORT} ^CN$ RewriteRule ^(.*)$ https://google.com.au [L] Options -Indexes RewriteRule ^wp-admin/install\.php$ - [F] RewriteRule ^wp-admin/includes/ - [F] RewriteRule !^wp-includes/ - [S=3] RewriteRule ^wp-includes/[^/]+\.php$ - [F] RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F] RewriteRule ^wp-includes/theme-compat/ - [F] RewriteRule ^wp\-content/uploads/.*\.(?:php[1-7]?|pht|phtml?|phps)$ - [NC,F] RewriteRule ^wp\-content/themes/.*\.(?:php[1-7]?|pht|phtml?|phps)$ - [NC,F] RewriteCond %{QUERY_STRING} https?: [OR] RewriteCond %{QUERY_STRING} (<|%3C)script(>|%3E) [NC,OR] RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|%3D) [NC,OR] RewriteCond %{QUERY_STRING} base64_decode\( [NC,OR] RewriteCond %{QUERY_STRING} %24&x [NC,OR] RewriteCond %{QUERY_STRING} (encode|localhost|loopback) [NC,OR] RewriteCond %{QUERY_STRING} (concat|insert|union|declare) [NC,OR] RewriteCond %{QUERY_STRING} %[01][0-9A-F] [NC] RewriteCond %{QUERY_STRING} !^loggedout=true RewriteCond %{QUERY_STRING} !^action=jetpack-sso RewriteCond %{QUERY_STRING} !^action=rp RewriteCond %{HTTP_COOKIE} !wordpress_logged_in_ RewriteCond %{HTTP_REFERER} !^http://maps\.googleapis\.com RewriteRule ^.* - [F] allow from all # deny from entries can go anywhere from here... An example is shown. # attacks - msn deny from 20.160.0.0/12 <Files wp-login.php> order deny,allow allow from xxx.xxx.xxx.xxx yyy.yyy.yyy.yyy deny from all </Files> <Files xmlrpc.php> order deny,allow allow from xxx.xxx.xxx.xxx allow from yyy.yyy.yyy.yyy deny from all </Files> <Files wp-cron.php> order deny,allow allow from xxx.xxx.xxx.xxx yyy.yyy.yyy.yyy deny from all </Files> <Files admin-ajax.php> order allow,deny allow from all satisfy any </Files> <Files wp-config.php> Order allow,deny allow from xxx.xxx.xxx.xxx yyy.yyy.yyy.yyy Deny from all </Files> <Files error_log> Order allow,deny allow from xxx.xxx.xxx.xxx yyy.yyy.yyy.yyy Deny from all </Files> <files .htaccess> <IfModule mod_authz_core.c> Require all denied </IfModule> <IfModule !mod_authz_core.c> Order allow,deny Deny from all </IfModule> </files> <files readme.html> <IfModule mod_authz_core.c> Require all denied </IfModule> <IfModule !mod_authz_core.c> Order allow,deny Deny from all </IfModule> </files> # Disable Directory Browsing Options -Indexes
If you install a caching plugin, such as W3 Total Cache, adding Browser Cache will add important caching instructions to your .htaccess file.
It is however possible to manually add those same entries (less any branding or specific plugin entries) but it is easier to let the plugin do it.
define('WP_MEMORY_LIMIT', '256M'); define('DISALLOW_FILE_EDIT', true); define( 'ALLOW_UNFILTERED_UPLOADS', true ); define('AUTOSAVE_INTERVAL', 300); /** define('DISABLE_WP_CRON', true); */ define( 'WPMS_ON', true ); define( 'WPMS_SMTP_PASS', 'SMTPPASSWORD' );