{"id":38852,"date":"2020-10-06T08:15:54","date_gmt":"2020-10-05T23:15:54","guid":{"rendered":"https:\/\/jirak.net\/wp\/automating-installation-of-wordpress-with-nginx-unit-on-ubuntu\/"},"modified":"2020-10-06T09:35:03","modified_gmt":"2020-10-06T00:35:03","slug":"automating-installation-of-wordpress-with-nginx-unit-on-ubuntu","status":"publish","type":"post","link":"https:\/\/jirak.net\/wp\/automating-installation-of-wordpress-with-nginx-unit-on-ubuntu\/","title":{"rendered":"Automating Installation of WordPress with NGINX Unit on Ubuntu"},"content":{"rendered":"<p>Automating Installation of WordPress with NGINX Unit on Ubuntu<br \/>\n<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/jirak.net\/wp\/wp-content\/uploads\/2020\/10\/automating-installation-WordPress-Unit_topology.png\" width=\"1024\" height=\"523\"><\/p>\n<p>There is an incredible amount of information out there about installing WordPress&nbsp;&ndash; a Google search for &#8220;WordPress install&#8221; yields&nbsp;488,000 results as of this writing. Yet among those results are very few tutorials that comprehensively explain how to install both WordPress and the underlying operating system in a way that&#8217;s maintainable in the long term. Perhaps that&#8217;s because the right configuration varies so much depending on specific needs, or perhaps it&#8217;s because a comprehensive guide doesn&#8217;t make for an <span>easy-to-read<\/span> article. <\/p>\n<p>In this post we&#8217;re trying to combine the best of both worlds, by providing a <a target=\"_blank\" href=\"https:\/\/gist.github.com\/nginx-gists\/bdc7da70b124c4f3e472970c7826cccc\" rel=\"noopener noreferrer\"><code>bash<\/code> script that automates WordPress installation<\/a> on Ubuntu and walking through it to explain what each section does and the design trade&#8209;offs we made. (If you are an advanced user, you may want to skip this post and <a target=\"_blank\" href=\"https:\/\/gist.github.com\/nginx-gists\/bdc7da70b124c4f3e472970c7826cccc\" rel=\"noopener noreferrer\">go directly to the script<\/a>, downloading and modifying it for your environment.) The resulting WordPress installation is scriptable, supports Let\u2019s&nbsp;Encrypt, uses NGINX&nbsp;Unit, and has production&#8209;ready settings.<\/p>\n<p>We&#8217;re building on the architecture for deploying WordPress with NGINX&nbsp;Unit that&#8217;s described in an <a href=\"https:\/\/www.nginx.com\/blog\/installing-wordpress-with-nginx-unit\/\">earlier post on our blog<\/a>, while also installing and configuring features not covered there (or in many other tutorials):<\/p>\n<ul>\n<li>The WordPress CLI<\/li>\n<li>Let\u2019s&nbsp;Encrypt and TLS\/SSL certificates<\/li>\n<li>Automated Let\u2019s&nbsp;Encrypt renewals<\/li>\n<li>NGINX caching<\/li>\n<li>NGINX compression<\/li>\n<li>NGINX HTTPS and HTTP\/2<\/li>\n<li>Process automation<\/li>\n<\/ul>\n<p>This post describes setting up WordPress on a single node where the static asset web server, PHP processing server, and database are co&#8209;located. Installation of a multi&#8209;host, multi&#8209;service WordPress configuration is a potential future topic. What else would you like us to cover in a future post? Let us know in the comments section at the end of the blog!<\/p>\n<h2>Prerequisites<\/h2>\n<ul>\n<li>A compute instance&nbsp;&ndash; container (<a target=\"_blank\" href=\"https:\/\/linuxcontainers.org\/lxc\/getting-started\/\" rel=\"noopener noreferrer\">LXC<\/a> or <a target=\"_blank\" href=\"https:\/\/linuxcontainers.org\/lxd\/getting-started-cli\/\" rel=\"noopener noreferrer\">LXD<\/a>), virtual machine, or bare metal machine&nbsp;&ndash; with at least&nbsp;512 MB of available memory, running Ubuntu&nbsp;18.04 or later<\/li>\n<li>Ports&nbsp;80 and&nbsp;443 on the compute instance are open to the Internet<\/li>\n<li>A domain name already associated with the compute instance\u2019s public IP address<\/li>\n<li><code>root<\/code> privileges or equivalent access via <code>sudo<\/code><\/li>\n<\/ul>\n<h2>Architecture Overview<\/h2>\n<p>The application architecture is the same as described in the <a href=\"https:\/\/www.nginx.com\/blog\/installing-wordpress-with-nginx-unit\/#\nArchitecture-Overview\">previous blog post<\/a>: a three&#8209;tier web application. It includes PHP scripts that must be executed by a PHP processor and static files that have to be delivered by a web server.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.nginx.com\/wp-content\/uploads\/2020\/10\/automating-installation-WordPress-Unit_topology.png\" alt=\"\" width=\"1024\" height=\"523\" class=\"aligncenter size-full wp-image-65157\" style=\"border:2px solid #666666;padding:2px;margin:2px\" \/><\/p>\n<h2>General Principles<\/h2>\n<ul>\n<li>Many of the configuration commands in the script are wrapped in conditionals (<code>if<\/code> statements), for idempotency: the script can run multiple times without risk of changing settings that are already correct.<\/li>\n<li>The script installs software from repositories wherever possible. This enables you to apply security patches to the system with a single command (for Ubuntu, <code>apt<\/code>&nbsp;<code>upgrade<\/code>).<\/li>\n<li>The commands make a best effort to detect if they are running in a container and change their configuration accordingly.<\/li>\n<li>When specifying the number of processes or threads to run in the configuration, the script provides a best guess of automatic configuration parameters that work in containers, virtual machines, or bare metal machines (on <a href=\"#Configuring-NGINX-Unit\">line&nbsp;278 for NGINX&nbsp;Unit<\/a> and <a href=\"#Configuring-Core-NGINX-Parameters\">line&nbsp;306 for NGINX<\/a>).<\/li>\n<li>We&#8217;ve written the configuration with an automation&#8209;first approach, which we hope can serve as a model for creating your own reusable infrastructure as code.<\/li>\n<li>All commands run as <code>root<\/code> because they change the core system configuration, but in the runtime state WordPress runs under a regular user identity.<\/li>\n<\/ul>\n<h2>Setting Environment Variables<\/h2>\n<p>Before running the script, set the following environment variables. <\/p>\n<ul>\n<li><code>WORDPRESS_DB_PASSWORD<\/code>&nbsp;&ndash; The password for the WordPress database.<\/li>\n<li><code>WORDPRESS_ADMIN_USER<\/code>&nbsp;&ndash; The username of the WordPress administrator.<\/li>\n<li><code>WORDPRESS_ADMIN_PASSWORD<\/code>&nbsp;&ndash; The password for the WordPress administrator.<\/li>\n<li><code>WORDPRESS_ADMIN_EMAIL<\/code>&nbsp;&ndash; The email address of the WordPress administrator.<\/li>\n<li><code>WORDPRESS_URL<\/code>&nbsp;&ndash; The full URL for the WordPress site, starting with <strong>https:\/\/<\/strong>.<\/li>\n<li><code>LETS_ENCRYPT_STAGING<\/code>&nbsp;&ndash; Blank by default, but set to&nbsp;<code>1<\/code> if you are using staging Let\u2019s&nbsp;Encrypt servers, which is necessary when frequently testing new deployments of your configuration. Otherwise, Let\u2019s&nbsp;Encrypt might block your IP address temporarily for making excessive requests.<\/li>\n<\/ul>\n<p>The script checks that the WordPress&#8209;related variables are set and exits if any are not (<span>lines 8&ndash;42<\/span>, not shown here). <a href=\"#Configuring-Certbot\"><span>Lines 572&ndash;576<\/span><\/a> check the value of <code>LETS_ENCRYPT_STAGING<\/code>.<\/p>\n<h2>Setting Derived Environment Variables<\/h2>\n<p>The script (<span>lines 55&ndash;61<\/span>, not shown here)<\/span> sets the following environment variables, either to a hardcoded value or a value derived from variables you set in the previous section.<\/p>\n<ul>\n<li><code>DEBIAN_FRONTEND=\"noninteractive\"<\/code>&nbsp;&ndash; Indicates to applications that an automated script is executing commands and no user interaction is possible.<\/li>\n<li><code>WORDPRESS_CLI_VERSION=\"2.4.0\"<\/code>&nbsp;&ndash; The version of the WordPress CLI to download.<\/li>\n<li><code>WORDPRESS_CLI_MD5= \"dedd5a662b80cda66e9e25d44c23b25c\"<\/code>&nbsp;&ndash; The cryptographic checksum for the WordPress CLI&nbsp;2.4.0 binary (the version specified by the <code>WORDPRESS_CLI_VERSION<\/code> variable). <a href=\"#Installing-the-WordPress-CLI-Utility\">Line&nbsp;162<\/a> uses the value to verify that the correct WordPress CLI version was downloaded.<\/li>\n<li><code>UPLOAD_MAX_FILESIZE=\"16M\"<\/code>&nbsp;&ndash; The maximum size of a file that can be uploaded to WordPress. This setting is used in several places in the configuration and it is useful to have it centrally defined.<\/li>\n<li><code>TLS_HOSTNAME= \"$(echo ${WORDPRESS_URL} | cut -d'\/' -f3)\"<\/code>&nbsp;&ndash; The system&#8217;s addressable hostname, extracted from the <code>WORDPRESS_URL<\/code> variable. It is used to fetch the appropriate TLS\/SSL certificates from Let\u2019s&nbsp;Encrypt and when WordPress needs to ping itself (see <a href=\"#Adding-the-WordPress-Site-Hostname-to-etc-hosts\">Adding the WordPress Site Hostname to <strong>\/etc\/hosts<\/strong><\/a>).<\/li>\n<li><code>NGINX_CONF_DIR=\"\/etc\/nginx\"<\/code>&nbsp;&ndash; The path to the directory containing the NGINX configuration and the primary configuration file, <strong>nginx.conf<\/strong>.<\/li>\n<li><code>CERT_DIR=\"\/etc\/letsencrypt\/live\/${TLS_HOSTNAME}\"<\/code>&nbsp;&ndash; The path to the Let\u2019s&nbsp;Encrypt certificates for the WordPress site\u2019s hostname, derived from the <code>TLS_HOSTNAME<\/code> variable.<\/li>\n<\/ul>\n<h2>Assigning the WordPress Site Hostname to the Compute Instance<\/h2>\n<p>The script sets the hostname of the compute instance to match the domain name of the WordPress site. This is optional and not needed in every configuration, but is useful when sending outgoing emails via SMTP in a single&#8209;host setup like the one configured by the script.<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"63-67\"><\/code><!-- 63 # Change the hostname to be the same as the WordPress hostname\n 64 if [ ! \"$(hostname)\" == \"${TLS_HOSTNAME}\" ]; then \n 65  echo \"\u25b6 Changing hostname to ${TLS_HOSTNAME}\"\n 66  hostnamectl set-hostname \"${TLS_HOSTNAME}\"\n 67 fi --><\/p>\n<h2 id=\"Adding-the-WordPress-Site-Hostname-to-etc-hosts\">Adding the WordPress Site Hostname to <strong>\/etc\/hosts<\/strong><\/h2>\n<p>The <a target=\"_blank\" href=\"https:\/\/developer.wordpress.org\/plugins\/cron\/\" rel=\"noopener noreferrer\">WP&#8209;Cron<\/a> plug&#8209;in is used by WordPress to run scheduled tasks, and requires that WordPress can ping itself over HTTP. To ensure WP&#8209;Cron works well in all environments, the script adds an entry to <strong>\/etc\/hosts<\/strong> so that WordPress can route to itself over the <a target=\"_blank\" href=\"https:\/\/askubuntu.com\/questions\/247625\/what-is-the-loopback-device-and-how-do-i-use-it\" rel=\"noopener noreferrer\">local loopback interface<\/a>. <\/p>\n<p> <code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"69-73\"><\/code><!-- 69 # Add the hostname to \/etc\/hosts\n 70 if [ \"$(grep -m1 \"${TLS_HOSTNAME}\" \/etc\/hosts)\" = \"\" ]; then\n 71    echo \"\u25b6 Adding hostname ${TLS_HOSTNAME} to \/etc\/hosts so that WordPress can ping itself\"\n 72  printf \"::1 %sn127.0.0.1 %sn\" \"${TLS_HOSTNAME}\" \"${TLS_HOSTNAME}\" &gt;-->&gt; \/etc\/hosts<br \/>\n 73 fi &#8211;&gt;<\/p>\n<h2>Installing Tools Needed for Later Steps<\/h2>\n<p>Later parts of the script use certain utilities and assume the repository index is updated. We update the repo index (line&nbsp;77) and install the required tools at this point <span>(lines 78&ndash;84)<\/span>.<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"75-84\"><\/code><!-- 75 # Make sure tools needed for install are present\n 76 echo \"\u25b6 Installing prerequisite tools\"\n 77 apt-get -qq update\n 78 apt-get -qq install -y \n 79    bc \n 80    ca-certificates \n 81    coreutils \n 82    curl \n 83    gnupg2 \n 84    lsb-release --><\/p>\n<h2>Adding the NGINX Unit and NGINX Open Source Repositories<\/h2>\n<p>The script installs NGINX&nbsp;Unit and NGINX Open Source from the official NGINX repositories to ensure we always have the latest security updates and bug fixes.<\/p>\n<p>Here the script installs the NGINX&nbsp;Unit repo <span>(lines 87&ndash;91)<\/span> and NGINX Open Source repo <span>(lines 94&ndash;98)<\/span> by adding a signing key to the system and a file to the <code>apt<\/code> configuration that defines the repository\u2019s location on the Internet. <\/p>\n<p>Actual installation of NGINX&nbsp;Unit and NGINX Open source happens in the <a href=\"#Installing-Dependencies\">next section<\/a>. We&#8217;re adding the repositories beforehand to avoid updating metadata multiple times, thereby making the overall installation faster.<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"86-98\"><\/code><!-- 86 # Install NGINX Unit repository\n 87 if [ ! -f \/etc\/apt\/sources.list.d\/unit.list ]; then\n 88   echo \"\u25b6 Installing NGINX Unit repository\"\n 89   curl -fsSL https:\/\/nginx.org\/keys\/nginx_signing.key | apt-key add -\n 90   echo \"deb https:\/\/packages.nginx.org\/unit\/ubuntu\/ $(lsb_release -cs) unit\" &gt;--> \/etc\/apt\/sources.list.d\/unit.list<br \/>\n 91 fi<br \/>\n 92<br \/>\n 93 # Install NGINX repository<br \/>\n 94 if [ ! -f \/etc\/apt\/sources.list.d\/nginx.list ]; then<br \/>\n 95    echo &#8220;\u25b6 Installing NGINX repository&#8221;<br \/>\n 96    curl -fsSL https:\/\/nginx.org\/keys\/nginx_signing.key | apt-key add &#8211;<br \/>\n 97    echo &#8220;deb https:\/\/nginx.org\/packages\/mainline\/ubuntu $(lsb_release -cs) nginx&#8221; &gt; \/etc\/apt\/sources.list.d\/nginx.list<br \/>\n 98 fi &#8211;&gt;<\/p>\n<h2 id=\"Installing-Dependencies\">Installing NGINX, NGINX Unit, PHP MariaDB, Certbot (Let\u2019s Encrypt), and Dependencies<\/h2>\n<p>With all the repositories in place, we update the repository metadata and install the applications. The packages installed by the script include the <a target=\"_blank\" href=\"https:\/\/make.wordpress.org\/hosting\/handbook\/handbook\/server-environment\/#php-extensions\" rel=\"noopener noreferrer\">PHP extensions<\/a> that are recommended when running WordPress.<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"100-102,111-131\"><\/code><!-- 100 echo \"\u25b6 Updating repository metadata\"\n101 apt-get -qq update\n102\n111 # Install PHP with dependencies and NGINX Unit\n112 echo \"\u25b6 Installing PHP, NGINX Unit, NGINX, Certbot, and MariaDB\"\n113 apt-get -qq install -y -no-install-recommends \n114   certbot \n115   python3-certbot-nginx \n116   php-cli \n117    php-common \n118   php-bcmath \n119   php-curl \n120   php-gd \n121   php-imagick \n122   php-mbstring \n123   php-mysql \n124   php-opcache \n125   php-xml \n126   php-zip \n127   ghostscript \n128   nginx \n129   unit \n130   unit-php \n131   mariadb-server --><\/p>\n<h2>Configuring PHP for Use with NGINX Unit and WordPress<\/h2>\n<p>The script creates a configuration file in the PHP <strong>conf.d<\/strong> directory <span>(lines 136&ndash;147)<\/span>. It sets the maximum size of uploaded files for PHP (line&nbsp;142), directs PHP errors to <code>STDERR<\/code> (line&nbsp;145) so that they get recorded in the NGINX&nbsp;Unit log, and restarts NGINX&nbsp;Unit (line&nbsp;151).<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"133-151\"><\/code><!-- 133 # Find the major and minor PHP version so that we can write to its conf.d directory\n134 PHP_MAJOR_MINOR_VERSION=\"$(php -v | head -n1 | cut -d' ' -f2 | cut -d'.' -f1,2)\"\n135\n136 if [ ! -f \"\/etc\/php\/${PHP_MAJOR_MINOR_VERSION}\/embed\/conf.d\/30-wordpress-overrides.ini\" ]; then\n137     echo \"\u25b6 Configuring PHP for use with NGINX Unit and WordPress\"\n138     # Add PHP configuration overrides\n139     cat &gt;--> &#8220;\/etc\/php\/${PHP_MAJOR_MINOR_VERSION}\/embed\/conf.d\/30-wordpress-overrides.ini&#8221; &lt;<\/p>\n<h2>Initializing the WordPress MariaDB Database<\/h2>\n<p>We&#8217;ve opted to use MariaDB instead of MySQL as the WordPress database. MariaDB has a more active open source community behind it and arguably offers <a target=\"_blank\" href=\"https:\/\/blog.kernl.us\/2019\/10\/wordpress-database-performance-showdown-mysql-vs-mariadb-vs-percona\/\" rel=\"noopener noreferrer\">better performance out of the box<\/a>. <\/p>\n<p>The script initializes the new database and creates credentials for WordPress to access it over the local loopback address.<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"153-156\"><\/code><!-- 153 # Set up WordPress database\n154 echo \"\u25b6 Configuring MariaDB for WordPress\"\n155 mysqladmin create wordpress || echo \"Ignoring above error because database may already exist\"\n156 mysql -e \"GRANT ALL PRIVILEGES ON wordpress.* TO \"wordpress\"@\"localhost\" IDENTIFIED BY \"$WORDPRESS_DB_PASSWORD\"; FLUSH PRIVILEGES;\" --><\/p>\n<h2>Installing the WordPress CLI Utility<\/h2>\n<p>Now the script installs the <a target=\"_blank\" href=\"https:\/\/wp-cli.org\/\" rel=\"noopener noreferrer\">WP&#8209;CLI utility<\/a>. Using it to install and manage WordPress gives you full control over WordPress configuration without having to manually modify files, update the database, or navigate to the WordPress administrator\u2019s panel. You can also use it to install themes or plug&#8209;ins, and to upgrade WordPress.<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"158-164\"><\/code><!-- 158 if [ ! -f \/usr\/local\/bin\/wp ]; then\n159   # Install the WordPress CLI\n160   echo \"\u25b6 Installing the WordPress CLI tool\"\n161   curl -retry 6 -Ls \"https:\/\/github.com\/wp-cli\/wp-cli\/releases\/download\/v${WORDPRESS_CLI_VERSION}\/wp-cli-${WORDPRESS_CLI_VERSION}.phar\" &gt;--> \/usr\/local\/bin\/wp<br \/>\n162   echo &#8220;$WORDPRESS_CLI_MD5 \/usr\/local\/bin\/wp&#8221; | md5sum -c &#8211;<br \/>\n163   chmod +x \/usr\/local\/bin\/wp<br \/>\n164 fi &#8211;&gt;<\/p>\n<h2>Installing and Configuring WordPress<\/h2>\n<p>The script installs the latest version of WordPress in the <strong>\/var\/www\/wordpress<\/strong> directory and configures these settings:<\/p>\n<ul>\n<li>The database connects over the Unix domain socket loopback interface instead of the TCP loopback interface, to reduce the amount of TCP traffic (line&nbsp;175).<\/li>\n<li>WordPress adds the <strong>https:\/\/<\/strong> prefix to URLs when clients connect to NGINX over HTTPS, and passes the remote hostname (as provided by NGINX) to PHP. We use a snippet of PHP code for this configuration <span>(lines 180&ndash;189)<\/span>.<\/li>\n<li>WordPress requires HTTPS for login (line&nbsp;194).<\/li>\n<li>The default URL structure for WordPress is resource&#8209;based (line&nbsp;201)<\/span>.<\/li>\n<li>Correct filesystem permissions are applied to the WordPress directory <span>(lines 207&ndash;210)<\/span>.<\/li>\n<\/ul>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"166-211\"><\/code><!-- 166 if [ ! -d \/var\/www\/wordpress ]; then\n167   # Create WordPress directories\n168   mkdir -p \/var\/www\/wordpress\n169   chown -R www-data:www-data \/var\/www\n170\n171   # Download WordPress using the WordPress CLI\n172   echo \"\u25b6 Installing WordPress\"\n173   su -s \/bin\/sh -c 'wp -path=\/var\/www\/wordpress core download' www-data\n174\n175    WP_CONFIG_CREATE_CMD=\"wp -path=\/var\/www\/wordpress config create -extra-php -dbname=wordpress -dbuser=wordpress -dbhost=\"localhost:\/var\/run\/mysqld\/mysqld.sock\" -dbpass=\"${WORDPRESS_DB_PASSWORD}\"\"\n176 \n177   # This snippet is injected into the wp-config.php file when it is created.\n178   # It informs WordPress that we are behind a reverse proxy and as such\n179   # allow it to generate links using https.\n180   cat &gt;--> \/tmp\/wp_forwarded_for.php &lt;<\/p>\n<h2>Configuring NGINX Unit<\/h2>\n<p>The script configures NGINX&nbsp;Unit to run PHP and handle WordPress paths, isolates PHP process namespaces, and tunes performance settings. There are three noteworthy features:<\/p>\n<ol>\n<li>\n<p>Namespace support is conditionally defined, based on whether the script is being run in a container <span>(lines 213&ndash;224)<\/span>. This is necessary because most container configurations do not support running additional containers inside of themselves.<\/p>\n<\/li>\n<li>\n<p>When namespace support is enabled, the <code>network<\/code> namespace is disabled (line&nbsp;218). This is necessary to enable WordPress both to hit its own endpoints and call out to the Internet.<\/p>\n<\/li>\n<li>\n<p>The maximum number of processes is calculated <span>(lines 226&ndash;228)<\/span> using the following algorithm: <strong>(Available memory with MariaDB and NGINX&nbsp;Unit running) \/ <span>(PHP memory limit + 5)<\/strong>.<\/span> The value is then set in the NGINX&nbsp;Unit configuration on <span>lines 277&ndash;280<\/span>.<\/p>\n<p>This value ensures there are always at least two PHP processes running, which is important because WordPress makes many asynchronous callouts to itself and without the additional process, operations like WP&#8209;Cron fail. You may need to increase or decrease this setting based on your particular WordPress configuration, because the setting generated here is conservative. On many production systems, settings between&nbsp;10 and&nbsp;100 are common.<\/li>\n<\/ol>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"213-298\"><\/code><!-- 213 if [ \"${container:-unknown}\" != \"lxc\" ] &amp;&amp; [ \"$(grep -m1 -a container=lxc \/proc\/1\/environ | tr -d '{$content}')\" == \"\" ]; then\n214     NAMESPACES='\"namespaces\": {\n215         \"cgroup\": true,\n216         \"credential\": true,\n217         \"mount\": true,\n218         \"network\": false,\n219         \"pid\": true,\n220         \"uname\": true\n221     }'\n222 else\n223     NAMESPACES='\"namespaces\": {}'\n224 fi\n225\n226 PHP_MEM_LIMIT=\"$(grep 'memory_limit' \/etc\/php\/7.4\/embed\/php.ini | tr -d ' ' | cut -f2 -d= | numfmt -from=iec)\"\n227 AVAIL_MEM=\"$(grep MemAvailable \/proc\/meminfo | tr -d ' kB' | cut -f2 -d: | numfmt -from-unit=K)\"\n228 MAX_PHP_PROCESSES=\"$(echo \"${AVAIL_MEM}\/${PHP_MEM_LIMIT}+5\" | bc)\"\n229 echo \"\u25b6 Calculated the maximum number of PHP processes as ${MAX_PHP_PROCESSES}. You may want to tune this value due to variations in your configuration. It is not unusual to see values between 10-100 in production configurations.\" \n230 \n231 echo \"\u25b6 Configuring NGINX Unit to use PHP and WordPress\"\n232 cat &gt;--> \/tmp\/wordpress.json &lt;<\/p>\n<h2>Configuring NGINX<\/h2>\n<ul>\n<li><a href=\"#Configuring-Core-NGINX-Parameters\">Configuring Core NGINX Parameters<\/a><\/li>\n<li><a href=\"#Configuring-NGINX-Compression-Settings\">Configuring NGINX Compression Settings<\/a><\/li>\n<li><a href=\"#Configuring-NGINX-Parameters-for-WordPress\">Configuring NGINX Parameters for WordPress<\/a><\/li>\n<\/ul>\n<h3 id=\"Configuring-Core-NGINX-Parameters\">Configuring Core NGINX Parameters<\/h3>\n<p>The script creates a directory for NGINX cache directory (line&nbsp;301), then creates the main NGINX <strong>nginx.conf<\/strong> configuration file <span>(lines 304&ndash;341)<\/span>. Among the configuration settings are the number of NGINX worker processes (line&nbsp;306) and maximum upload size (line&nbsp;325). Line&nbsp;329 imports the compression configuration defined in the <a href=\"#Configuring-NGINX-Compression-Settings\">next section<\/a>, and <span>lines 332&ndash;337<\/span> set caching parameters.<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"300-341\"><\/code><!-- 300 # Make directory for NGINX cache\n301 mkdir -p \/var\/cache\/nginx\/proxy\n302 \n303 echo \"\u25b6 Configuring NGINX\"\n304 cat &gt;--> ${NGINX_CONF_DIR}\/nginx.conf &lt;<\/p>\n<h3 id=\"Configuring-NGINX-Compression-Settings\">Configuring NGINX Compression Settings<\/h3>\n<p>Compressing content on the fly before sending it to clients is a good way to improve website performance, but only if compression is configured correctly. The script uses a configuration <span>(lines 346&ndash;414)<\/span> from the <a target=\"_blank\" href=\"https:\/\/github.com\/h5bp\/server-configs-nginx\/blob\/master\/h5bp\/web_performance\/compression.conf\" rel=\"noopener noreferrer\">h5bp repository on GitHub<\/a>.<\/p>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"343-415\"><\/code><!-- 343 cat &gt;--> ${NGINX_CONF_DIR}\/gzip_compression.conf &lt;<\/p>\n<h3 id=\"Configuring-NGINX-Parameters-for-WordPress\">Configuring NGINX Parameters for WordPress<\/h3>\n<p>Next the script creates an NGINX configuration file called <strong>default.conf<\/strong> in the <strong>conf.d<\/strong> directory with settings for WordPress <span>(lines 417&ndash;541). The configuration:<\/p>\n<ul>\n<li>Enables TLS certificates obtained from Let\u2019s&nbsp;Encrypt via Certbot (<span>lines 445&ndash;447;<\/span> for the Certbot configuration, see the <a href=\"#Configuring-Certbot\">next section<\/a>)<\/li>\n<li>Implements TLS security settings defined as best practices by Let\u2019s&nbsp;Encrypt (<span>lines 449&ndash;454)<\/span><\/li>\n<li>Enables caching of proxied requests for&nbsp;1 hour by default (line&nbsp;458)<\/li>\n<li>Disables access logging, as well as error logging when the asset is not found, for two commonly requested files, <strong>favicon.ico<\/strong> and <strong>robots.txt<\/strong> <span>(lines 465&ndash;474)<\/span><\/li>\n<li>Denies access to hidden files and certain <strong>.php<\/strong> files, to prevent illegal access or inadvertent execution <span>(lines 476&ndash;500)<\/span><\/li>\n<li>Disables access logging of static file content and font files <span>(lines 503&ndash;510)<\/span><\/li>\n<li>Sets the <span><a target=\"_blank\" href=\"https:\/\/stackoverflow.com\/questions\/5008944\/how-to-add-an-access-control-allow-origin-header\" rel=\"noopener noreferrer\"><code>Access-Control-Allow-Origin<\/code> header<\/a><\/span> for font files (line&nbsp;508)<\/li>\n<li>Applies routing settings for the <strong>index.php<\/strong> file and other static files <span>(lines 512&ndash;539)<\/span><\/li>\n<\/ul>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"417-541\"><\/code><!-- 417 cat &gt;--> ${NGINX_CONF_DIR}\/conf.d\/default.conf &lt;<\/p>\n<h2 id=\"Configuring-Certbot\">Configuring Certbot for Let\u2019s Encrypt Certificates and Auto Renewal<\/h2>\n<p><a target=\"_blank\" href=\"https:\/\/certbot.eff.org\/about\/\" rel=\"noopener noreferrer\">Certbot<\/a> is a free tool from the Electronic Frontier Foundation (EFF) that obtains and auto&#8209;renews TLS certificates from Let\u2019s&nbsp;Encrypt. The script performs the following actions which configure Certbot to handle Let\u2019s&nbsp;Encrypt certificates for NGINX:<\/p>\n<ul>\n<li>Stops NGINX (line&nbsp;544)<\/li>\n<li>Downloads TLS parameters with the currently recommended settings <span>(lines 550&ndash;564)<\/span><\/li>\n<li>Runs Certbot to retrieve the TLS certificates for the site <span>(lines 578&ndash;585)<\/span><\/li>\n<li>Restarts NGINX to use the TLS certificates (line&nbsp;588)<\/li>\n<li>Configures Certbot to run every day <span>at 3:24 a.m.<\/span> and check whether the TLS certificates need to be renewed; if so, it downloads the new certifications and reloads NGINX <span>(lines 591&ndash;594)<\/span><\/li>\n<\/ul>\n<p><code data-gist-id=\"bdc7da70b124c4f3e472970c7826cccc\" data-gist-file=\"ubuntu_install.sh\" data-gist-line=\"543-594\"><\/code><!-- 543 echo \"\u25b6 Stopping NGINX in order to set up Let's Encrypt\"\n544 service nginx stop\n545 \n546 mkdir -p \/var\/www\/certbot\n547 chown www-data:www-data \/var\/www\/certbot\n548 chmod g+s \/var\/www\/certbot\n549 \n550 if [ ! -f ${NGINX_CONF_DIR}\/options-ssl-nginx.conf ]; then\n551   echo \"\u25b6 Downloading recommended TLS parameters\"\n552  curl -retry 6 -Ls -z \"Tue, 14 Apr 2020 16:36:07 GMT\" \n553     -o \"${NGINX_CONF_DIR}\/options-ssl-nginx.conf\" \n554  \"https:\/\/raw.githubusercontent.com\/certbot\/certbot\/master\/certbot-nginx\/certbot_nginx\/_internal\/tls_configs\/options-ssl-nginx.conf\" \n555     || echo \"Couldn't download latest options-ssl-nginx.conf\"\n556 fi\n557\n558 if [ ! -f ${NGINX_CONF_DIR}\/ssl-dhparams.pem ]; then\n559   echo \"\u25b6 Downloading recommended TLS DH parameters\"\n560   curl -retry 6 -Ls -z \"Tue, 14 Apr 2020 16:49:18 GMT\" \n561     -o \"${NGINX_CONF_DIR}\/ssl-dhparams.pem\" \n562 \"https:\/\/raw.githubusercontent.com\/certbot\/certbot\/master\/certbot\/certbot\/ssl-dhparams.pem\" \n563     || echo \"Couldn't download latest ssl-dhparams.pem\"\n564 fi\n565\n566 # If tls_certs_init.sh hasn't been run before, let's remove the self-signed certs\n567 if [ ! -d \"\/etc\/letsencrypt\/accounts\" ]; then\n568   echo \"\u25b6 Removing self-signed certificates\"\n569   rm -rf \"${CERT_DIR}\"\n570 fi\n571\n572 if [ \"\" = \"${LETS_ENCRYPT_STAGING:-}\" ] || [ \"0\" = \"${LETS_ENCRYPT_STAGING}\" ]; then\n573   CERTBOT_STAGING_FLAG=\"\"\n574 else\n575   CERTBOT_STAGING_FLAG=\"-staging\"\n576 fi\n577\n578 if [ ! -f \"${CERT_DIR}\/fullchain.pem\" ]; then\n579   echo \"\u25b6 Generating certificates with Let's Encrypt\"\n580   certbot certonly -standalone \n581         -m \"${WORDPRESS_ADMIN_EMAIL}\" \n582          ${CERTBOT_STAGING_FLAG} \n583          -agree-tos -force-renewal -non-interactive \n584         -d \"${TLS_HOSTNAME}\"\n585 fi\n586 \n587 echo \"\u25b6 Starting NGINX in order to use new configuration\"\n588 service nginx start\n589\n590 # Write crontab for periodic Let's Encrypt cert renewal\n591 if [ \"$(crontab -l | grep -m1 'certbot renew')\" == \"\" ]; then\n592   echo \"\u25b6 Adding certbot to crontab for automatic Let's Encrypt renewal\"\n593   (crontab -l 2&gt;-->\/dev\/null; echo &#8220;24 3 * * * certbot renew &#8211;dry-run &#8211;nginx &#8211;post-hook &#8216;service nginx reload'&#8221;) | crontab &#8211;<br \/>\n594 fi &#8211;&gt;<\/p>\n<h2>Customizing Your WordPress Site<\/h2>\n<p>We&#8217;ve explained how our <code>bash<\/code> script configures NGINX Open Source and NGINX&nbsp;Unit to serve a production&#8209;ready website with TLS\/SSL enabled. Depending on your needs, you might want to customize your site further:<\/p>\n<ul>\n<li>Enable <a target=\"_blank\" href=\"https:\/\/github.com\/google\/ngx_brotli\" rel=\"noopener noreferrer\">Brotli<\/a>, for better <span>on-the-fly<\/span> compression performance over HTTPS<\/li>\n<li>Install <a target=\"_blank\" href=\"https:\/\/github.com\/SpiderLabs\/ModSecurity-nginx\" rel=\"noopener noreferrer\">ModSecurity<\/a> with <a target=\"_blank\" href=\"https:\/\/github.com\/Rev3rseSecurity\/wordpress-modsecurity-ruleset\" rel=\"noopener noreferrer\">WordPress rules<\/a> to prevent automated attacks on your site <\/li>\n<li><a target=\"_blank\" href=\"https:\/\/wordpress.org\/support\/article\/wordpress-backups\/\" rel=\"noopener noreferrer\">Set up a backup procedure<\/a> for WordPress that works for your needs<\/li>\n<li><a target=\"_blank\" href=\"https:\/\/www.inguardians.com\/2017\/06\/08\/protecting-the-mr-robot-vuln-hub-machine-part-2-confining-wordpress-with-apparmor\/\" rel=\"noopener noreferrer\">Protect your WordPress installation<\/a> with <a target=\"_blank\" href=\"https:\/\/wiki.ubuntu.com\/AppArmor\" rel=\"noopener noreferrer\">AppArmor<\/a> (on Ubuntu)<\/li>\n<li>Install <a target=\"_blank\" href=\"http:\/\/www.postfix.org\/\" rel=\"noopener noreferrer\">Postfix<\/a> or <a target=\"_blank\" href=\"https:\/\/marlam.de\/msmtp\/\" rel=\"noopener noreferrer\"><code>msmtp<\/code><\/a> so that WordPress can send outbound emails<\/li>\n<li>Benchmark your site to get an understanding of how much traffic it can support<\/li>\n<\/ul>\n<p>For even better website performance, consider upgrading to <a href=\"https:\/\/www.nginx.com\/products\/nginx\">NGINX&nbsp;Plus<\/a>, our enterprise&#8209;grade, commercially supported product based on NGINX Open Source. NGINX&nbsp;Plus subscribers get the dynamically loaded <a href=\"https:\/\/www.nginx.com\/products\/nginx\/modules\/brotli\">Brotli module<\/a>, and (at an additional cost) the <a href=\"https:\/\/www.nginx.com\/products\/nginx\/modules\/nginx-waf\/\">NGINX ModSecurity WAF<\/a>. We also offer <a href=\"https:\/\/www.nginx.com\/products\/nginx-app-protect\/\">NGINX App Protect<\/a>, a WAF module for NGINX&nbsp;Plus based on F5&#8217;s industry&#8209;leading security technology. <\/p>\n<p>Want to try out the script with NGINX&nbsp;Plus? Start your <span><a href=\"https:\/\/www.nginx.com\/free-trial-request\">free 30-day trial<\/a><\/span> today or <a href=\"https:\/\/www.nginx.com\/contact-sales\/\">contact us to discuss your use cases<\/a>.<\/p>\n<p>The post <a rel=\"nofollow\" href=\"https:\/\/www.nginx.com\/blog\/automating-installation-wordpress-with-nginx-unit-on-ubuntu\/\">Automating Installation of WordPress with NGINX Unit on Ubuntu<\/a> appeared first on <a rel=\"nofollow\" href=\"https:\/\/www.nginx.com\">NGINX<\/a>.<\/p>\n<p>Source: <a href=\"https:\/\/www.nginx.com\/blog\/automating-installation-wordpress-with-nginx-unit-on-ubuntu\/\" target=\"_blank\" rel=\"noopener noreferrer\">Automating Installation of WordPress with NGINX Unit on Ubuntu<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"mh-excerpt\"><p>Automating Installation of WordPress with NGINX Unit on Ubuntu There is an incredible amount of information out there about installing WordPress&nbsp;&ndash; a Google search for &#8220;WordPress install&#8221; yields&nbsp;488,000 results as of this writing. Yet among those results are very few tutorials that comprehensively explain how to install both WordPress and the underlying operating system in a way that&#8217;s maintainable in the long term. Perhaps that&#8217;s because the right configuration varies so much depending on specific needs, or perhaps it&#8217;s because a comprehensive guide doesn&#8217;t make for an easy-to-read article. In this post we&#8217;re trying to combine the best of both worlds, by providing a bash script that automates WordPress installation on Ubuntu and walking through it to explain what each section does and the design trade&#8209;offs we made. (If you are an advanced user, you may want to skip this <a class=\"mh-excerpt-more\" href=\"https:\/\/jirak.net\/wp\/automating-installation-of-wordpress-with-nginx-unit-on-ubuntu\/\" title=\"Automating Installation of WordPress with NGINX Unit on Ubuntu\">[ more&#8230; ]<\/a><\/p>\n<\/div>","protected":false},"author":1,"featured_media":38853,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[169],"tags":[652],"class_list":["post-38852","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-news","tag-nginx"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/38852","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/comments?post=38852"}],"version-history":[{"count":1,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/38852\/revisions"}],"predecessor-version":[{"id":38854,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/posts\/38852\/revisions\/38854"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/media\/38853"}],"wp:attachment":[{"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/media?parent=38852"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/categories?post=38852"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jirak.net\/wp\/wp-json\/wp\/v2\/tags?post=38852"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}