Amazon Linux (CentOS), Apache, MySQL, PHP

Updated 20-Sep-2023

Note: Amazon Linux AMI is essentially CentOS 6.x. Everything below works on CentOS as well.

Preparation for Amazon LAMP

The OpenVPN on Amazon Linux AMI is a good place to start in preping the L for the AMP part of the lamp stack. Lots of good stuff there. A followup is WordPress Multisite on Amazon Linux AMI.

The Order of Things with Amazon Linux AMI

Amazon Linux AMI is a bit picky about the order of things. Otherwise there will be dependency conflicts, which is a rife problem. Also, make sure to disable (at least temporarily) other repositories, otherwise there can be even more dependency conflicts. Here is a handy script to ensure that an upgrade (or fresh install) will go well, starting with Apache httpd, php, and ssl:

service httpd stop
yum -y erase httpd httpd-tools apr apr-util
yum -y remove php*
yum -y install php56
yum -y install php56-xml php56-xmlrpc php56-soap php56-gd php56-mbstring
yum -y install php56-cli php56-common php56-pdo
yum -y install php56-mysqlnd
yum -y install php56-opcache
yum -y install mod24_ssl
sed -i -e 's/SSLMutex/Mutex/g' /etc/httpd/conf.d/ssl.conf
service httpd start
service httpd restart

Source: Gist Note that this is PHP 5.6, not the vaunted 7.x version. Version 5.6 is (as of July, 2017) the most popular PHP version, and (in a recent study) is installed on 22.4% of all servers whose server-side programming languages are known, compared with 5.4% of all 7.x versions.

Enable httpd Service and Check if Enabled

Enable httpd as a service to startup

chkconfig httpd on

Check the configuration status of httpd

chkconfig --list httpd

Set User and Group Rights

This is especially useful when using sftp.

usermod -a -G apache USERNAME
chown -R USERNAME:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;

Create and Test PHP Info Page

echo "" > /var/www/html/phpinfo.php

Note that there are at least three command-line ways of figuring out things such as which php.ini file am I using: - php --ini, - php -i | grep "Configuration File", and - apachectl -S

Copy opcache.php OpCache Status Page

cd /root/temp
wget https://raw.githubusercontent.com/rlerdorf/opcache-status/master/opcache.php
mv opcache.php /var/www/html/opcache.php

Test at:

https://host.server.tld/opcache.php

Apache Configuration

Spend time on the following files (shortcut scripts are shown after filename): - nano /etc/httpd/conf/httpd.conf - eap - nano /etc/httpd/conf.d/ssl.conf - essl - nano /etc/httpd/conf.modules.d/00-mpm.conf - empm - sudo nano /etc/httpd/conf.modules.d/00-base.conf - ebase

/etc/httpd/conf/httpd.conf Modifications

## Modifications at Top of File ##
RewriteEngine On
ServerTokens Prod
ServerSignature Off
TraceEnable off
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
Header always append X-Frame-Options DENY
Header set X-XSS-Protection "1; mode=block"
Header unset ETag
Header set Cache-Control "max-age=604800, must-revalidate"
FileETag None
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
Header append Vary User-Agent
RewriteCond %{REQUEST_METHOD} ^(TRACE|DELETE|TRACK) [NC]
RewriteRule ^.* - [F]
HostnameLookups Off
RequestHeader unset Proxy early
ServerTokens ProductOnly
## End Modifications ##
...
## Modifications inside directory ##
# Strip all subdomains not including status.
# NOTE: Disable this while subdomains help get to sites
#RewriteCond %{HTTP_HOST} !^status\.(.*)\.(.*) [NC]
#RewriteCond %{HTTP_HOST} ^(.*)\.(.*)\.(.*) [NC]
#RewriteRule ^(.*) https://%2.%3/$1 [R=301,L]
# Require SSL (site-wide)
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{HTTP_HOST}/$1 [R=301,L]
# Redirect Alias Domains (after SSL rewrite)
RewriteCond %{HTTP_HOST} ^domain.tld$ [NC]
RewriteRule ^(.*)$ https://domain2.tld2/$1 [R=301,L]
# WordPress
RewriteBase /
RewriteRule ^index\.php$ - [L]
# Obfuscate the /admin/ and wp-login.php stuff
RewriteRule ^(/)?SoMeWeIrDsHiT/?$ /wp-login.php [QSA,L]
RewriteRule ^(/)?wp-register-php/?$ /wplogin?action=register [QSA,L]
# Add a trailing slash to /wp-admin
RewriteRule ^wp-admin$ wp-admin/ [R=301,L]
# More WordPress Multisite
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^(wp-(content|admin|includes).*) $1 [L]
RewriteRule ^(.*\.php)$ $1 [L]
RewriteRule . index.php [L]
## End Modifications ##
...
## Modification after files section ##
# Disable XML-RPC


        Require all denied


        Order allow,deny
        Deny from all


## End Modifications ##
...
## Modification inside mime_module ##
AddType text/plain .txt
AddType text/html .html
AddType text/xml .xml
AddType text/css .css
AddType application/x-javascript .js
AddType image/x-icon .ico
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE image/x-icon
## End Modification ##
...
## Modification at End of File ##
#ErrorDocument 500 "The server made a boo boo."
#ErrorDocument 404 /missing.html

  StartServers            2
  MinSpareServers         4
  MaxSpareServers         8
  MaxClients             64
  MaxRequestsPerChild  2560

## End Modification ##

MPM Prefork rule of thumb is ram/15 for max clients, but test that out. The above is for a server with 1gb ram, though with a database on the same server, may need to throttle this back. From another server (with Apache installed), test with Apache Bench (ab), for example:

ab -n 2000 -c 200 https://host.domain.tld:443/info.php

/etc/httpd/conf.d/ssl.conf Modifications

## Modifications at Top ##
SSLStrictSNIVHostCheck  off
SSLUseStapling                   on
SSLStaplingResponderTimeout  5
SSLStaplingReturnResponderErrors off
SSLStaplingCache                 shmcb:/run/ocsp(128000)
## End of Modifications ##
...
## VirtualHost Modifications ##
SSLCipherSuite         ALL:+HIGH:+TLSv1:!ADH:!EXP:!SSLv2:!MEDIUM:!LOW:!NULL:!aNULL
SSLCompression off
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
SSLVerifyClient none
## End of Modifications ##

Install apachetop for Amazon Linux

Because Amazon Linux AMI is still basically a CentOS 6.x base, and most repositories have a CentOS 7.x version of apachetop, it is important to download and install locally as follows:

mkdir /root/temp
cd /root/temp
wget http://dl.fedoraproject.org/pub/epel/6/i386//apachetop-0.15.6-1.el6.i686.rpm
yum -y localinstall apachetop-0.15.6-1.el6.i686.rpm

OpCache Configuration

First, comment out the opcache from /etc/php.ini

nano /etc/php.ini

As follows:

;declared in opcache.ini
;zend_extension = opcache.so

Next, configure the opcache.ini file:

nano /etc/php-5.6.d/10-opcache.ini

Configure as follows:

zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
;128 is for 1gb ram
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=6400
;opcache.max_wasted_percentage=5
opcache.use_cwd=1
opcache.validate_timestamps=0
opcache.revalidate_freq=60
;opcache.revalidate_path=0
;opcache.save_comments=1
;opcache.load_comments=1
opcache.fast_shutdown=1
;opcache.enable_file_override=0
;opcache.optimization_level=0xffffffff
;opcache.inherited_hack=1
;opcache.dups_fix=0
opcache.blacklist_filename=/etc/php-5.6.d/opcache*.blacklist
;opcache.max_file_size=0
;opcache.consistency_checks=0
;opcache.force_restart_timeout=180
;opcache.error_log=
;opcache.log_verbosity_level=1
;opcache.preferred_memory_model=
;opcache.protect_memory=0

PHP.INI Configuration

[PHP]
;already declared in opcache.ini
;zend_extension = opcache.so
engine = On
;for piwik
always_populate_raw_post_data=-1
short_open_tag = Off
asp_tags = Off
precision = 14
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
;unserialize_callback_func =
serialize_precision = 17
;open_basedir =
;disable_functions =
;disable_classes =
zend.enable_gc = On
;zend.multibyte = Off
;zend.script_encoding =
expose_php = Off
max_execution_time = 30
max_input_time = 60
memory_limit = 256M
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = On
variables_order = "GPCS"
request_order = "GP"
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 8M
;auto_prepend_file =
;auto_append_file =
default_mimetype = "text/html"
;default_charset = "UTF-8"
;doc_root =
;user_dir =
enable_dl = Off
file_uploads = On
upload_max_filesize = 20M
max_file_uploads = 20
allow_url_fopen = On
allow_url_include = Off
default_socket_timeout = 60
;;;;;;;;;;;;;;;;;;;
; Module Settings ;
;;;;;;;;;;;;;;;;;;;
[CLI Server]
cli_server.color = On
[Date]
date.timezone = Asia/Tokyo
[Pdo_mysql]
pdo_mysql.cache_size = 2000
pdo_mysql.default_socket=
[mail function]
smtp_port = 25
; For Unix only. You may supply arguments as well (default: "sendmail -t -i").
; http://php.net/sendmail-path
; sendmail_path = "/usr/sbin/sendmail -t -i"
; Force the addition of the specified parameters to be passed as extra parameters
; to the sendmail binary. These parameters will always replace the value of
; the 5th parameter to mail(), even in safe mode.
;mail.force_extra_parameters =
; Add X-PHP-Originating-Script: that will include uid of the script followed by the filname
mail.add_x_header = On
; The path to a log file that will log all mail() calls. Log entries include
; the full path of the script, line number, To address and headers.
;mail.log =
[SQL]
sql.safe_mode = Off
[ODBC]
odbc.allow_persistent = On
odbc.check_persistent = On
odbc.max_persistent = -1
odbc.max_links = -1
odbc.defaultlrl = 4096
odbc.defaultbinmode = 1
[Interbase]
ibase.allow_persistent = 1
ibase.max_persistent = -1
ibase.max_links = -1
ibase.timestampformat = "%Y-%m-%d %H:%M:%S"
ibase.dateformat = "%Y-%m-%d"
ibase.timeformat = "%H:%M:%S"
[MySQL]
mysql.allow_local_infile = On
mysql.allow_persistent = On
mysql.cache_size = 2000
mysql.max_persistent = -1
mysql.max_links = -1
mysql.default_port =
mysql.default_socket =
mysql.default_host =
mysql.default_user =
mysql.default_password =
mysql.connect_timeout = 60
mysql.trace_mode = Off
[MySQLi]
mysqli.max_persistent = -1
mysqli.allow_persistent = On
mysqli.max_links = -1
mysqli.cache_size = 2000
mysqli.default_port = 3306
mysqli.default_socket =
mysqli.default_host =
mysqli.default_user =
mysqli.default_pw =
mysqli.reconnect = Off
[mysqlnd]
mysqlnd.collect_statistics = On
mysqlnd.collect_memory_statistics = Off
[PostgreSQL]
pgsql.allow_persistent = On
pgsql.auto_reset_persistent = Off
pgsql.max_persistent = -1
pgsql.max_links = -1
pgsql.ignore_notice = 0
pgsql.log_notice = 0
[Sybase-CT]
sybct.allow_persistent = On
sybct.max_persistent = -1
sybct.max_links = -1
sybct.min_server_severity = 10
sybct.min_client_severity = 10
[bcmath]
bcmath.scale = 0
[Session]
session.save_handler = files
session.save_path = "/var/lib/php/session"
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 1440
session.bug_compat_42 = Off
session.bug_compat_warn = Off
session.referer_check =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.hash_function = 0
session.hash_bits_per_character = 5
url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry"
[MSSQL]
mssql.allow_persistent = On
mssql.max_persistent = -1
mssql.max_links = -1
mssql.min_error_severity = 10
mssql.min_message_severity = 10
mssql.compatability_mode = Off
mssql.secure_connection = Off
tidy.clean_output = Off
soap.wsdl_cache_enabled=1
soap.wsdl_cache_dir="/tmp"
soap.wsdl_cache_ttl=86400
soap.wsdl_cache_limit = 5
ldap.max_links = -1

Install LetsEncrypt SSL Certs

If OpenSSL is installed and the default settings are in httpd.conf and ssl.conf, then LetsEncrypt should work in a straightforward way. Do add the various virtual server and virtual aliases in both files, and for the ./letsencrypt-auto command, include all hosts adding on -d host.domain.tld one after the other. Also, for Amazon Linux AMI have to include the --debug flag.

yum -y install git
git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
cd /opt/letsencrypt
./letsencrypt-auto --debug --apache -d host.domain.com

Note: Use the staging environment for testing before doing a live run. It can save you an hour's wait, if there are things to work out.

Get the Certs into the HSTS Preload List

PHP Configuration

php.ini - stuff - more stuff ... MORE ...

MySQL 5.6 vs. MySQL 5.7

As of mid-July, 2017 there are two main options for MySQL: version 5.6 or version 5.7. MySQL 5.6 is available from the AMI repository, but 5.7 only from Oracle's community repository. Many people have complained about this, as they well should. MariaDB 10.x is a competitor, but without much adoption. The speed and functionality improvements of 5.7 make it the desired distribution release. The issue with the community repository is that it is unclear which distribution to use on Amazon Linux AMI. The standard redhat and fedora distributions don't work because they assume systemd (e.g., RHEL/CentOS 7.x). Some sources suggest using the generic linux distribution. Another option is the RHEL/CentOS 6.x build. Both seem viable, as long as 7.x repositories and distributions are disabled.

RDS with MySQL and Aurora

Besides installing and configuring a database on an EC2 or Lightsail instance (same thing), there are other Amazon database server options. The Amazon Relaltional Database Service (RDS), supports multiple engines including: - RDS for MySQL - RDS for Aurora Note that along with these there are other options such as nosql, redis, etc., and also various caching options.

For a basic MySQL installation (not on RDS, a separate database server), installing via a yum repository makes a shortcut.

MySQL 5.7 on Amazon Linux AMI standard installation

Install some new dependencies, if not present:

yum -y install libaio
yum -y install numactl

Download the repository (see latest version numbers here):

wget http://dev.mysql.com/get/mysql57-community-release-el6-11.noarch.rpm
yum -y localinstall mysql57-community-release-el6-11.noarch.rpm

Check to see if repo is installed, which ones are installed, and which available:

yum repolist | grep mysql

Read more about installing and enabling these MySQL repositories. Run the installation command, which will also install various common, libs, client, and server packages.

yum -y install mysql-community-server

Start the service

service mysqld start

Grep the error log which will have the generated password

grep 'temporary password' /var/log/mysqld.log

Login with that password

mysql –u root –p

Type in the password Then, change password

ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPassword';

Check to see if the password is hashed in the database, and also the version of MySQL installed.

select user,host,authentication_string from mysql.user;
select @@version,@@basedir,@@datadir;

Exit out of mysqld

exit

Set mysqld to start on boot, and see if it is set correctly:

chkconfig mysqld on
chkconfig --list mysqld

PhpMyAdmin on Amazon Linux

Install PhpMyAdmin from the Amazon EPEL Repository:

yum -y install phpmyadmin

Edit the file /etc/httpd/conf.d/phpMyAdmin.conf and replace 127.0.0.1 with the ip address of the client.

nano /etc/httpd/conf.d/phpMyAdmin.conf

Restart the web server and database server (to be safe)

service httpd restart
service mysqld restart

Test to see if it works

https://host.domain.tld/phpmyadmin/