{"id":382,"date":"2015-12-07T06:07:22","date_gmt":"2015-12-07T14:07:22","guid":{"rendered":"https:\/\/cloudinsidr.com\/content\/?p=382"},"modified":"2022-03-21T06:48:16","modified_gmt":"2022-03-21T13:48:16","slug":"lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora","status":"publish","type":"post","link":"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/","title":{"rendered":"The LEMP stack: NGINX, MariaDB\/MySQL, PHP 7.x on RHEL\/Fedora\/CentOS with SELinux"},"content":{"rendered":"<p>The setup of the LEMP stack (NGINX, MariaDB or MySQL, and PHP) has many caveats. They can impact both performance and security.<\/p>\n<p>Here is how to LEMP (not limp along!).<\/p>\n<p><!--more--><\/p>\n<h2>Set up NGINX<\/h2>\n<p>The easiest way to set up NGINX is by installing the official repository by the creators of NGINX.<\/p>\n<h3>Add the NGINX repository<\/h3>\n<p>Create a file named\u00a0nginx.repo and save\u00a0it in the directory:<\/p>\n<pre class=\"lang:sh decode:true\">\/etc\/yum.repos.d\/<\/pre>\n<p>Populate the newly created file with the following content (for CentOS):<\/p>\n<pre class=\"\">[nginx]\r\nname=nginx repo\r\nbaseurl=http:\/\/nginx.org\/packages\/centos\/$releasever\/$basearch\/\r\ngpgcheck=0\r\nenabled=1<\/pre>\n<p>For RHEL, use the above repo configuration with the following baseurl:<\/p>\n<pre class=\"lang:sh decode:true\">baseurl=http:\/\/nginx.org\/packages\/rhel\/$releasever\/$basearch\/<\/pre>\n<p>In case of both CentOS and RHEL, you need to manually replace the variable $releasever with the major release number of your OS; for CentOS 7, replace the variable with a\u00a07. The same principle goes for RHEL. When in doubt, visit the repository in your web browser, navigate to the appropriate directory, and grab its\u00a0URL as the baseurl of your repo.<\/p>\n<h3>Install NGINX binaries<\/h3>\n<p>Once the repository is in place, you can begin the installation of NGINX binaries:<\/p>\n<pre class=\"lang:sh decode:true \">yum install nginx<\/pre>\n<p>After that, it&#8217;s all about configuration tweaks.<\/p>\n<h3>Recommended\u00a0configuration options for NGINX<\/h3>\n<p>Optimizing the settings of NGINX is an art as much as it is a science.<\/p>\n<p>If nginx is already running, the command:<\/p>\n<pre class=\"lang:sh decode:true\">systemctl status nginx.service<\/pre>\n<p>will yield useful information including the location of is main config file (usually \/etc\/nginx\/nginx.conf). This\u00a0file may\u00a0reference additional, site-specific config files. Together, they combine into one set of\u00a0configuration directives divided into so-called blocks that immediately follow a couple of general directives (on worker processes, worker connections, and the like, that apply to the current master process).<\/p>\n<p>There are three types of\u00a0blocks in NGINX:<\/p>\n<ul>\n<li>a\u00a0<strong>http {}<\/strong> block (one per master process),<\/li>\n<li>multiple <strong>server {}<\/strong> blocks, which may represent virtual hosts (they are always placed outside\u00a0the\u00a0http {} block), and<\/li>\n<li>one or multiple <strong>location {}<\/strong> blocks, always\u00a0inside a\u00a0server {} block.<\/li>\n<\/ul>\n<h4>Point the server to the document root<\/h4>\n<p>The <strong>document root <\/strong>(also known as the web server root) is the directory tree that contains files and directories\u00a0that are made available for your web server to deliver to visitors.<\/p>\n<p>Your PHP engine may, in certain circumstances, traverse higher-up\u00a0directories, so you must be very smart about the location of your web server root.<\/p>\n<p>You want your web content to reside\u00a0in either <span class=\"lang:sh decode:true crayon-inline \">\/var\/www\/<\/span>\u00a0, <span class=\"lang:sh decode:true crayon-inline \">\/srv<\/span>\u00a0, or <span class=\"lang:sh decode:true crayon-inline\">\/usr\/share\/www<\/span>\u00a0.<\/p>\n<p>Never use system directories, the home directory of the root user on the system, or the root directory (\/) to serve web content! It&#8217;s a no-no. This practice opens up a can of worms and could result in a disaster, should\u00a0a visitor manage to\u00a0coerce your\u00a0web server\/your PHP engine to traverse directories up the directory tree (which isn&#8217;t impossible, as you will learn later in this article).<\/p>\n<p>The document root should be defined\u00a0<strong>within the server {} block<\/strong> of your site&#8217;s configuration file.<\/p>\n<pre class=\"font:monaco font-size:14 lang:sh decode:true\">server {\r\n    server_name www.yourdomain.com;\r\n    root \/var\/www\/nginx-default\/;\r\n    location \/ {\r\n        # [...]\r\n    }\r\n    location \/foo {\r\n        # [...]\r\n    }\r\n    location \/bar {\r\n        # [...]\r\n    }\r\n}<\/pre>\n<p>Do NOT place your\u00a0document root in the standard document root directory that is part of the installation of NGINX on your system. This directory may be overwritten during an upgrade and there is no recourse for data loss. &#8220;You should have known better&#8221; (or so the theory), so don&#8217;t put anything that&#8217;s dear to you\u00a0there.<\/p>\n<figure id=\"attachment_454\" aria-describedby=\"caption-attachment-454\" style=\"width: 591px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/nginx_default_directory_\/\" target=\"_blank\" rel=\"attachment noopener wp-att-454\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-454 size-full\" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_default_directory_.png\" alt=\"The default webserver document directory of NGINX with an example website\" width=\"591\" height=\"164\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_default_directory_.png 591w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_default_directory_-300x83.png 300w\" sizes=\"(max-width: 591px) 100vw, 591px\" \/><\/a><figcaption id=\"caption-attachment-454\" class=\"wp-caption-text\">The default webserver document directory of NGINX with an example website: don&#8217;t put your files here!<\/figcaption><\/figure>\n<h4>Tell the server how to listen to incoming requests<\/h4>\n<p>Set your listen directive correctly, for example (&#8220;listen on port 443 on all network interfaces&#8221;):<\/p>\n<pre class=\"\">server {\r\n <strong>listen<\/strong> 443 ssl http2; # IPv4\r\n <strong>listen<\/strong> [::]:443 ssl http2; # IPv6<\/pre>\n<p><strong>Never use a\u00a0hostname<\/strong> in your server&#8217;s <strong>listen<\/strong> directive and avoid doing so in an <strong>upstream<\/strong> location (you only need an upstream location when you are using NGINX as an HTTP load balancer). Using an\u00a0IP address instead of a hostname is a lot safer because NGINX will be able to bind to it even if it fails to\u00a0resolve the hostname, for example:<\/p>\n<pre class=\"\">upstream {\r\n    server http:\/\/10.48.41.12;\r\n}\r\n\r\nserver {\r\n    listen 127.0.0.16:80;\r\n    # [...]\r\n}<\/pre>\n<h4>Configure routing requests to virtual hosts based on server_name<\/h4>\n<p>If NGINX listens to requests directed at several virtual servers on only one port, it has to decide how to route these requests based on the header alone. To accomplish this, NGINX uses the server_name directive (&#8220;respond to requests directed to these host names based on this server block, otherwise route to the default server&#8221;):<\/p>\n<pre class=\"\">server {\r\n    <strong>server_name<\/strong> www.cloudinsidr.com cloudinsidr.com;\r\n}<\/pre>\n<p>The first server block that NGINX encounters when parsing its configuration files will be its default server. If you don&#8217;t like that, use the <b>default_server <\/b>directive (a property of the listen port, NOT the server name; it defines the default server per port):<\/p>\n<pre>listen      443 <b>default_server<\/b>;<\/pre>\n<h4>Hide Nginx version number<\/h4>\n<p>Hide NGINX version number (put this in the http block in\u00a0\/etc\/nginx\/nginx.conf):<\/p>\n<pre class=\"\">server_tokens off;<\/pre>\n<h4>Permit only the most secure TLS versions<\/h4>\n<p>Don&#8217;t use SSLv3 and stay away from TLS v1\/1.1:<\/p>\n<pre class=\"\">ssl_protocols TLSv3 TLSv1.2;<\/pre>\n<h4>Specify\u00a0the index (only\u00a0once!)<\/h4>\n<p>Put the index directive either once in the http { } block, or once in each\u00a0server {} block, but not in both. For example:<\/p>\n<pre>http {\r\n    <strong>index<\/strong> index.php index.htm index.html;\r\n    server {\r\n        server_name www.yourdomain1.com yourdomain1.com;\r\n        location \/ {\r\n            # [...]\r\n        }\r\n    }\r\n    server {\r\n        server_name www.yourdomain2.com yourdomain2.com;\r\n        location \/ {\r\n            # [...]\r\n        }\r\n        location \/foo {\r\n            # [...]\r\n        }\r\n    }\r\n}<\/pre>\n<h4>Do not use If statements: use multiple server and location blocks<\/h4>\n<p>Do not use &#8216;If&#8217; statements (<em>if<\/em> you can help it, lol!); the &#8216;If&#8217;\u00a0implementation in NGINX was a half-baked attempt at appeasing some users who considered it a priority at the time. As a result, &#8216;If&#8217; statements\u00a0waste server resources and are terribly\u00a0inefficient.<\/p>\n<p>Instead of using ifs, use multiple server and location blocks. That&#8217;s what they are for. (If you want to know more about the danger of If statements, refer to this article: <em><a href=\"https:\/\/www.nginx.com\/resources\/wiki\/start\/topics\/depth\/ifisevil\/\">If Is Evil<\/a><\/em>)<\/p>\n<p>If your purpose for an If statement is checking whether a file exists, NGINX wants you to use\u00a0try_files instead:<\/p>\n<pre class=\"lang:sh decode:true\">server {\r\nroot \/var\/www\/www.domain.com;\r\nlocation \/ {\r\ntry_files $uri $uri\/ \/index.html;\r\n}\r\n}<\/pre>\n<p>This tells the server: try\u00a0$uri and serve it if found; if\u00a0$uri doesn&#8217;t exist, try the next best thing which is a directory of that name ($uri\/ with a forward slash at the end), and if that doesn&#8217;t exist either, then tough\u00a0luck, fall back to\u00a0\/index.html.<\/p>\n<p>Instead of using a file as the fallback location you can\u00a0use a status code (<code>=404<\/code>), but if you do point to\u00a0a file for fallback,\u00a0the file\u00a0<strong>must exist<\/strong>. One other thing worth noting: try_files will not issue an internal redirect for anything but the last parameter.<\/p>\n<p>You still need a location to catch \\.php$ URIs as otherwise try_files will serve an\u00a0$uri that is found to exist as a plain text file.<\/p>\n<p>To get a CMS system such as WordPress, Drupal or Joomla running, use this (adjust the query string for your CMS if appropriate):<\/p>\n<pre class=\"lang:sh decode:true \">try_files $uri $uri\/ \/index.php?q=$uri&amp;$args;<\/pre>\n<p>WordPress can even read from (REQUEST_URI):<\/p>\n<pre class=\"lang:sh decode:true \">try_files $uri $uri\/ \/index.php;<\/pre>\n<h4>Implementing redirects: do not use rewrites, use multiple (server) blocks and\u00a0the return directive<\/h4>\n<p>Do not use this syntax (it will cost you dearly in terms of precious performance):<\/p>\n<pre class=\"\"># DO NOT use this syntax!!! Use the return directive instead!\r\nrewrite ^\/(.*)$ http:\/\/exampledomain.tld\/$1 permanent;<\/pre>\n<p>Instead, put this in the relevant location {} block and it will get you much better results:<\/p>\n<pre class=\"\">return 301 https:\/\/exampledomain.tld$request_uri;<\/pre>\n<p>For example, if you want to redirect visitors from yoursite.tld using http to www.yoursite.tld using https, this is how to do it the NGINX way: create two separate server blocks and use the return directive like this:<\/p>\n<pre>server {\r\n    listen       80;\r\n    server_name  yoursite.tld;\r\n    return       301 https:\/\/www.yoursite.tld$request_uri;\r\n}\r\n\r\nserver {\r\n    listen       443 ssl http2;\r\n    listen       [::]:443 ssl http2; \r\n    server_name  www.yoursite.tld;\r\n    ...\r\n}<\/pre>\n<p>This will redirect all http traffic to yoursite.tld using http to www.yoursite.tld using https.<\/p>\n<p>To redirect\u00a0all traffic from your old domain(s) to your new domain (yournewdomain.tld), use multiple server {} blocks with a return directive (and, optionally, a catch-all server_name) like in this example:<\/p>\n<pre class=\"\">server {\r\n    listen       443;\r\n    server_name  yournewdomain.tld www.yournewdomain.tld;\r\n    ...\r\n}\r\n\r\nserver {\r\n    listen       80 <strong>default_server<\/strong>;\r\n    # the following line is a catch-all server name\r\n    server_name  _;\r\n    return       301 http:\/\/yournewdomain.tld$request_uri;\r\n}<\/pre>\n<p>If you are interested in using regular expressions in server names, try <em><a href=\"http:\/\/nginx.org\/en\/docs\/http\/server_names.html\" target=\"_blank\" rel=\"noopener\">this<\/a><\/em>.<\/p>\n<p>How to activate HTTP\/2 with https is covered in this post: <a href=\"https:\/\/cloudinsidr.com\/content\/how-to-activate-http2-with-ssltls-encryption-in-nginx-for-secure-connections\/\">How to Activate HTTP\/2 with TLS Encryption in NGINX for Secure Connections without a Performance Penalty<\/a>.<\/p>\n<h4>Disallow\u00a0the execution of PHP files in any directory that allows uploads by a user<\/h4>\n<p>Disallowing the execution of PHP files in any directory that contains user uploads is a good practice on any web server. With NGINX, this is how to best accomplish it:<\/p>\n<pre class=\"lang:sh decode:true \">location \/path\/to\/uploads { location ~ \\.php$ {return 403;} # [...] }<\/pre>\n<p>You can also use the\u00a0try_files directive to filter out the problem condition:<\/p>\n<pre class=\"\">location ~* \\.php$ {\r\n    try_files $uri =404;\r\n    fastcgi_pass [backend];\r\n    # [...]\r\n}<\/pre>\n<p><span class=\"p\">or a nested location:<br \/>\n<\/span><\/p>\n<pre class=\"\">location ~* \\.php$ {\r\n    location ~ \\..*\/.*\\.php$ {return 404;}\r\n    fastcgi_pass [backend];\r\n    # [...]\r\n}<\/pre>\n<h4>(Optional, for the paranoid) Allow only specific scripts to\u00a0execute<\/h4>\n<p>If you want to allow only specific scripts to\u00a0execute, use this in your PHP location block ([backend] is your backend of choice):<\/p>\n<pre class=\"\">location ~* (file_a|file_b|file_c)\\.php$ {\r\n    fastcgi_pass [backend];\r\n    # [...]\r\n}<\/pre>\n<p>The parenthesis in the above example contain a list of allowed script files, delimited with a pipe to signify an OR statement.<\/p>\n<h4>Use a proxy the right way<\/h4>\n<p>If you use a proxy, do it right. One way is this:<\/p>\n<pre class=\"\">server {\r\n server_name _;\r\n root \/var\/www\/site;\r\n location \/ {\r\n try_files $uri $uri\/ @proxy;\r\n }\r\n location @proxy {\r\n include fastcgi_params;\r\n fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\r\n fastcgi_pass unix:\/tmp\/phpcgi.socket;\r\n }\r\n}<\/pre>\n<p>This will also work fine:<\/p>\n<pre>server {\r\nserver_name _;\r\nroot \/var\/www\/site;\r\nlocation \/ {\r\ntry_files $uri $uri\/ \/index.php;\r\n}\r\nlocation ~ \\.php$ {\r\ninclude fastcgi_params;\r\nfastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\r\nfastcgi_pass unix:\/tmp\/phpcgi.socket;\r\n}\r\n}<\/pre>\n<p>In the above example, if the URI is not a file or directory, it gets\u00a0passed on to your proxy.<\/p>\n<h4>Enable NGINX on startup<\/h4>\n<p>Verify the status of NGINX after installation:<\/p>\n<pre class=\"lang:sh decode:true \">systemctl status nginx<\/pre>\n<p>Enable automatic launch of NGINX on system startup:<\/p>\n<pre class=\"lang:sh decode:true \">systemctl enable nginx<\/pre>\n<p>You should see output indicating that a symbolic link has been created by the utility:<\/p>\n<pre class=\"lang:sh decode:true \">ln -s '\/usr\/lib\/systemd\/system\/nginx.service' '\/etc\/systemd\/system\/multi-user.target.wants\/nginx.service'<\/pre>\n<h4>Start NGINX<\/h4>\n<p>Start NGINX (once):<\/p>\n<pre class=\"lang:sh decode:true\">service nginx start<\/pre>\n<p>or<\/p>\n<pre class=\"lang:sh decode:true \">systemctl start nginx<\/pre>\n<p>At this point, NGINX should be able to serve static pages, assuming that file system permissions and SELinux security contexts are correctly set on the web server document directory (continue reading for more, and see <a href=\"https:\/\/www.cloudinsidr.com\/content\/tip-of-the-day-how-to-find-the-correct-selinux-security-contexts-and-adjust-selinux-labels-on-your-linux-system\/\" target=\"_blank\" rel=\"noopener\">this post<\/a>\u00a0for some tips on SELinux).<\/p>\n<p>Using your web browser, navigate\u00a0to the IP address of the web server, or to the domain\/host name (if correctly configured). You should see the NGINX Welcome page.<\/p>\n<h4 class=\"lang:sh decode:true \">Tips for troubleshooting connectivity problems<\/h4>\n<p>Aside from the obvious things like a rogue firewall or a missing DNS resolver, there are a couple of things you need to be aware of.<\/p>\n<p>On AWS, the command to find public addresses of your server (ip addr) will yield the &#8220;internal&#8221; private IPv4, not your public IPv4 addresses, in addition to any IPv6 addresses (those are always both &#8220;private&#8221; and &#8220;public&#8221;). In the listen directive of NGINX, you can use the private IPv4 addresses and IPv6, but not public IPv4 as your instance is unaware of them.<\/p>\n<p>Restart nginx whenever you need to\u00a0apply changes:<\/p>\n<pre class=\"lang:sh decode:true\">service nginx restart<\/pre>\n<p>When in doubt, clear the browser cache and flush the DNS cache of your local machine.<\/p>\n<figure id=\"attachment_2306\" aria-describedby=\"caption-attachment-2306\" style=\"width: 688px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/ipconfig_flushdns-2\/\" rel=\"attachment wp-att-2306\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2306 \" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/ipconfig_flushdns.png\" alt=\"ipconfig \/flushdns on Windows 10\" width=\"688\" height=\"285\" \/><\/a><figcaption id=\"caption-attachment-2306\" class=\"wp-caption-text\">ipconfig \/flushdns on Windows 10<\/figcaption><\/figure>\n<h3>Setting correct permissions for NGINX<\/h3>\n<p>There are two aspects to setting access permissions: Unix\/GNU Linux file system permissions and SELinux.<\/p>\n<p>It is considered a good practice to run NGINX as the user and group nginx. The installation script may have created them; to verify whether such a user and group exist, use this command:<\/p>\n<pre>cat \/etc\/passwd<\/pre>\n<p>If these users exist, skip to the section &#8220;Create website owners\u00a0and set ownership on the web server document tree&#8221; below.<\/p>\n<h4>Create the user and group nginx<\/h4>\n<p>Create a group named nginx:<\/p>\n<pre>groupadd nginx<\/pre>\n<p>If such a user does not yet exist in your \/etc\/passwd file, create a new system user (-r) named nginx and set that user&#8217;s shell (-s) to\u00a0\/sbin\/nologin so no one can start an interactive shell session with\u00a0nginx&#8217; credentials:<\/p>\n<pre class=\"lang:sh decode:true \">useradd -s \/sbin\/nologin -r -d \/var\/cache\/nginx nginx<\/pre>\n<p>The user is created with a home directory located in\u00a0\/var\/cache\/nginx. (Feel free to empty\u00a0this\u00a0directory if it gets populated from the skeleton directory. The contents of \/var\/cache\/nginx are\u00a0owned by and 777 (rwx) accessible to\u00a0the user nginx with root as the group at 0 (&#8212;) permissions and others at 0 (&#8212;) as well. The parent directory is owned by root:root with permissions set at 755. This is the default setup on CentOS 7. See section on SELinux below for advanced configuration.)<\/p>\n<p>Add the user nginx to the group nginx:<\/p>\n<pre class=\"lang:sh decode:true\">usermod -G nginx nginx<\/pre>\n<h4>Create website owners\u00a0and set ownership on the web server document tree<\/h4>\n<p>The ownership of files and directories inside of document root is of paramount importance when it comes to security.<\/p>\n<p>If you think it&#8217;s a good idea to assign\u00a0the web server document directory tree to the owner and group nginx (nginx:nginx), think again. This is not the safest way to go about web hosting. Rather, create a separate owner for each web server root directory, then assign the ownership to each site&#8217;s respective owner, then add each of these site owners to the group\u00a0nginx alongside the nginx user.<\/p>\n<p>For the simplest configuration possible,\u00a0you do not need an additional user to own the web server document directory. The user and group nginx:nginx will suffice. However, this is not a good practice when running <b><i>multiple\u00a0<\/i><\/b>websites: each website should have an\u00a0owner of its own and each of these owners should be a member of the nginx group.<\/p>\n<p>If you also happen to run PHP and want to grant\u00a0the web server the ability\u00a0to perform automatic updates of your web applications, then the SAPI service (php-fpm) must run with the permissions of the\u00a0user who owns\u00a0the contents of the installation\u00a0directory it will be asked to keep up to date.\u00a0This also means that you need to ensure that php-fpm starts\u00a0separate <strong>pools<\/strong> for each of the websites with the privileges of their respective owners. In the resulting setup, individual web applications will be running in a &#8220;sandboxed&#8221; environment of sorts. Should one of them become compromised, it will not spill over to the others.<\/p>\n<p><strong>Step 1. Create users to be owners of each website&#8217;s directory tree<\/strong><\/p>\n<p>For each website (or web application) that you want to &#8220;sandbox&#8221;, create a user named websiteowner1.\u00a0If you don&#8217;t want this user to have a home (assuming they won&#8217;t be signing in) or shell access, create it\u00a0as <span style=\"color: #ff0000;\">a system user<\/span> (-r) with no shell (-s):<\/p>\n<pre class=\"\">useradd websiteowner1 -r -s \/sbin\/nologin<\/pre>\n<p>To create a user with a home directory (for example, for remote access to the web server directory tree using SFTP with keys), use this command:<\/p>\n<pre class=\"lang:sh decode:true\">useradd -md \/home\/websiteowner1 -s \/sbin\/nologin websiteowner1<\/pre>\n<pre><\/pre>\n<p>It creates a user account and the user&#8217;s home directory (-m) without granting the user the right to a shell (-s \/sbin\/nologin) and without a private group (-n).<\/p>\n<p>If the use needs to perform uploads using SFTP, it also needs a shell:<\/p>\n<pre>usermod -s \/bin\/bash username<\/pre>\n<p style=\"padding-left: 30px;\">TIP: If you need to remove a user, userdel is helpful.<\/p>\n<p><strong>Step 2. Add website owners to the web server group<\/strong><\/p>\n<p>Add the user websiteowner1 to the group nginx:<\/p>\n<pre class=\"lang:sh decode:true \">usermod -G nginx websiteowner1<\/pre>\n<p>This user will own the document tree of the website&#8217;s root directory with the group set to nginx (websiteowner1:nginx).<\/p>\n<p><strong>Step 3. Correct the ownership on each website&#8217;s document directory and its contents<\/strong><\/p>\n<p>Set the\u00a0owner and and group of website 1 using the command (-R for recursive):<\/p>\n<pre class=\"lang:sh decode:true\">chown -Rf websiteowner1:nginx \/var\/www\/www.website1-root.tld<\/pre>\n<p>Repeat this procedure for each individual website that has its own NGINX root directory. Each site&#8217;s web root directory and its contents should be <strong>owned by the website owner<\/strong>\u00a0and <strong>read-execute-accessible to the group<\/strong> nginx (see section on SELinux for advanced configuration).<\/p>\n<h4>Step 4. Correct file access permissions on the web server directory and its contents<\/h4>\n<p>Once you adjust the ownership of the web server document directory for each website\/web application, you also need to set correct Unix <strong>access permissions<\/strong> on it (750 for directories, 640 for files).<\/p>\n<p>Which specific permissions you need for various objects depends on the type of content you intend to be serving.<\/p>\n<p><strong><span style=\"color: #ff0000;\">Rule No. 1: NEVER use 777.<\/span> <\/strong>(If 750\/640 permissions on directories\/files seem insufficient, SELinux could be the problem; for troubleshooting SELinux see further below.) Since the master process of NGINX runs with\u00a0root permissions and spawning worker processes as\u00a0user nginx (or a similar unprivileged user), if you were to set permissions to 777, you could enable any user to run any code as root. Do not do this!<\/p>\n<p><span style=\"color: #ff0000;\"><strong>Rule No. 2. The web server should never have the ability to modify the files it is executing.<\/strong><\/span><\/p>\n<p>The web server needs the following permissions on its <strong>document directory tree<\/strong>:<\/p>\n<ul>\n<li><strong>read (4)<\/strong> permissions on all files it is expected\u00a0to serve (as <strong>a member of the group<\/strong>);<\/li>\n<li><strong>read (4)<\/strong> and <strong>execute (1)<\/strong> permissions on the directories that contain files it is expected to serve (as <strong>a member of the group<\/strong>);<\/li>\n<\/ul>\n<p>The service that will be writing to the directory tree (php-fpm) needs the following permissions on files and directories contained within:<\/p>\n<ul>\n<li><strong>read (4)<\/strong>, <strong>write (2)<\/strong>, and <strong>execute (1)<\/strong> permissions on\u00a0upload directories (preferably\u00a0as the <strong>owner<\/strong>, NOT\u00a0a member of the group).<\/li>\n<\/ul>\n<p>In short:<\/p>\n<ul>\n<li>640 for files<\/li>\n<li>750\u00a0for directories,<\/li>\n<li>ownership of the directory and its contents by the website owner:\u00a0group with the member nginx (the same user that the worker processes run as).<\/li>\n<\/ul>\n<h5>Set correct permissions for interpreted scripts and directories<\/h5>\n<p><strong>Unlike binaries and shell scripts, interpreted scripts such as PHP or Ruby\u00a0work just fine without the execute permission (x).<\/strong> However, in order to traverse (enter) a directory, the\u00a0<strong>execute permission on that directory<\/strong>\u00a0is nonetheless required. The web server needs this permission to list the contents of a directory or serve any files that are located inside of it.<\/p>\n<p>To set permissions on the web server document directory tree for files and directories, use these commands:<\/p>\n<pre class=\"lang:sh decode:true\">find \/var\/path\/to\/web\/directory -type f -exec chmod 640 {} \\;\r\nfind \/var\/path\/to\/web\/directory -type d -exec chmod 750 {} \\;<\/pre>\n<p>All files receive the permissions 640\u00a0(-rw-r&#8212;&#8211;) and all directories are 750 (drwxr-x&#8212;). Because the group nginx is assigned\u00a0to all files and directories, the user nginx who belongs to this group alongside the website owners can <strong>read<\/strong> these files, but (unlike the website owners) <strong>cannot write<\/strong> to them. Directories that\u00a0must be written to, such as the upload directory, are a special case and will be discussed later below.<\/p>\n<p><span style=\"color: #ff0000;\">WARNING:<\/span> Be careful to apply the above permissions to the home directory of each site and its contents only. Changing the parent path in this way will produce the dreaded &#8220;No input file specified.&#8221; error. This simply means that NGINX is denied access to the web server document directory (see Step 5 below for details). The correct permissions on it are:<\/p>\n<pre>drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 www<\/pre>\n<h4><strong>Step 5. Verify\u00a0if NGINX is allowed to traverse the directory tree down to the root directory of each site<\/strong><\/h4>\n<p>To traverse (enter) a directory and list its contents, the web server needs the <strong>execute<\/strong> permission (x) on that <strong>directory<\/strong> and all its parent directories. When it comes to serving content from a\u00a0directory, it also needs the <strong>read<\/strong> permission on the <strong>files<\/strong> contained within it.\u00a0Granting the web server the execute permission for the directory tree and the read permission for files as a member of the group (not the owner, and not &#8220;other&#8221; users) should suffice.<\/p>\n<p>Verify that NGINX is allowed to traverse directories down to the web server document directory of each site:<\/p>\n<pre class=\"\">namei -l \/var\/www\/www.youwebsite.tld\/\r\nf: \/var\/www\/www.youwebsite.tld\/\r\ndr-xr-xr-x root root \/\r\ndrwxr-xr-x root root var\r\ndrwxr-xr-x nginx nginx www\r\ndrwxr-x--- websiteowner1 nginx www.youwebsite.tld<\/pre>\n<p>In the above\u00a0example, the x privilege allows everyone including &#8220;other users&#8221; to traverse the directory tree down to \/var\/www. \u00a0The website directory is accessible to the website owner and the group. The web server, as a member of this group, can traverse the directory (x) and list its contents.<\/p>\n<p>Once this is done, there are still a few things on the to-do list:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.cloudinsidr.com\/content\/tip-of-the-day-how-to-find-the-correct-selinux-security-contexts-and-adjust-selinux-labels-on-your-linux-system\/\" target=\"_blank\" rel=\"noopener\">correct <strong>SELinux\u00a0security labels<\/strong> (allowing writes where appropriate)<\/a>;<\/li>\n<li>ensure that\u00a0the service that is going to perform updates has ownership of the files; for php-fpm this means that the\u00a0pool that corresponds to each website\/web application runs as the user who is the\u00a0website&#8217;s <strong>owner (see further down)<\/strong>.<\/li>\n<\/ul>\n<h5>Special considerations for\u00a0PHP applications with the ability to write to the web server document directory<\/h5>\n<p>When running a CMS that is expected to have the ability to upload files (such as updates) you will have\u00a0to make special provisions in order to ensure security while giving some processes the ability to write to web server directories.<\/p>\n<p>Before you go there, however, you need to get PHP up and running.<\/p>\n<h3>Set Up PHP 7.x<\/h3>\n<p>Nginx uses php-fpm as it&#8217;s SAPI (server API). We covered the installation of\u00a0PHP 7.x in <a href=\"https:\/\/cloudinsidr.com\/content\/how-to-install-php-7-on-centos-7-red-hat-rhel-7-fedora\/\">this post<\/a>. In this post, you&#8217;ll learn how to configure it to work with NGINX.<\/p>\n<p>In short: install the remi repo, install PHP with modules, enable it to launch\u00a0during system startup, configure, restart, done!<\/p>\n<h4>Install the repository<\/h4>\n<p>In your web browser, navigate to the <a href=\"https:\/\/blog.remirepo.net\/pages\/Config-en\" target=\"_blank\" rel=\"noopener\">Remi repo<\/a> configuration wizard:<\/p>\n<pre>https:\/\/rpms.remirepo.net\/wizard\/<\/pre>\n<figure id=\"attachment_2308\" aria-describedby=\"caption-attachment-2308\" style=\"width: 801px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/remirepo_wizard\/\" rel=\"attachment wp-att-2308\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-2308\" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/RemiRepo_wizard.png\" alt=\"Remi Repo wizard\" width=\"801\" height=\"479\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/RemiRepo_wizard.png 801w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/RemiRepo_wizard-300x179.png 300w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/RemiRepo_wizard-768x459.png 768w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/RemiRepo_wizard-600x359.png 600w\" sizes=\"(max-width: 801px) 100vw, 801px\" \/><\/a><figcaption id=\"caption-attachment-2308\" class=\"wp-caption-text\">Remi Repo wizard<\/figcaption><\/figure>\n<p>Select the parameters of your desired installation and follow the directions.<\/p>\n<p>Here is an example for PHP 7.2.6 on Fedora 28.<\/p>\n<p>First, install the remi repository configuration package:<\/p>\n<pre class=\"lang:sh decode:true \">dnf install http:\/\/rpms.remirepo.net\/fedora\/remi-release-28.rpm<\/pre>\n<p>Next, install your PHP modules, for example:<\/p>\n<pre class=\"lang:sh decode:true\"><del>dnf --enablerepo=remi-php70 install php70-php-pear php70-php-bcmath php70-php-pecl-jsond-devel php70-php-mysqlnd php70-php-gd php70-php-common php70-php-fpm php70-php-intl php70-php-cli php70-php php70-php-xml php70-php-opcache php70-php-pecl-apcu php70-php-pecl-jsond php70-php-pdo php70-php-gmp php70-php-process php70-php-pecl-imagick php70-php-devel php70-php-mbstring php70-php-mcrypt<\/del><\/pre>\n<pre>dnf install php php-mcrypt php-cli php-gd php-curl php-mysql php-ldap php-zip php-fileinfo<\/pre>\n<h4>Enable\u00a0PHP launch on system startup<\/h4>\n<p>Start PHP once:<\/p>\n<pre class=\"lang:sh decode:true \">systemctl start php-fpm<\/pre>\n<p>Should the\u00a0PHP-FPM service fail to start, read <a href=\"https:\/\/cloudinsidr.com\/content\/troubleshooting-php-7-tcp-sockets-with-selinux-on-centos-7-rhelfedora\/\" target=\"_blank\" rel=\"noopener\">this post on troubleshooting SELinux for php-fpm<\/a>.<\/p>\n<p>Enable automatic launch on system startup:<\/p>\n<pre class=\"lang:sh decode:true \">systemctl enable php-fpm<\/pre>\n<p>You should see output resembling this line:<\/p>\n<pre class=\"lang:sh decode:true \">ln -s '\/usr\/lib\/systemd\/system\/php-fpm.service' '\/etc\/systemd\/system\/multi-user.target.wants\/php-fpm.service'<\/pre>\n<h4>Find info.php on your system<\/h4>\n<p>To find out the status of php-fpm, ask your system:<\/p>\n<pre class=\"lang:sh decode:true\">systemctl status php-fpm.service<\/pre>\n<figure id=\"attachment_2309\" aria-describedby=\"caption-attachment-2309\" style=\"width: 787px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/systemctl_status_php-fpm\/\" rel=\"attachment wp-att-2309\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2309 \" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/systemctl_status_php-fpm.png\" alt=\"Output of the command systemctl status php-fpm from Remi repo on Fedora 28\" width=\"787\" height=\"282\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/systemctl_status_php-fpm.png 1576w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/systemctl_status_php-fpm-300x107.png 300w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/systemctl_status_php-fpm-768x275.png 768w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/systemctl_status_php-fpm-1024x366.png 1024w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/systemctl_status_php-fpm-600x215.png 600w\" sizes=\"(max-width: 787px) 100vw, 787px\" \/><\/a><figcaption id=\"caption-attachment-2309\" class=\"wp-caption-text\">Output of the command systemctl status php-fpm from Remi repo on Fedora 28<\/figcaption><\/figure>\n<p>This means that the php-fpm.service is running with a master process pool spun in accordance with the configuration file:<\/p>\n<pre class=\"lang:sh decode:true\">\/etc\/php-fpm.conf<\/pre>\n<p>Navigate to the parent directory of the main php-fpm configuration\u00a0file on your system (php-fpm.conf). List the contents of this directory,\u00a0and you will find <strong>php.ini<\/strong>, the main configuration file that controls the behavior of the PHP interpreter, as well as various other configuration files including those that initiate pools:<\/p>\n<pre># ls -lat . | grep -i php\r\ndrwxr-xr-x. 2 root root 4096 Jun 18 15:50 php-fpm.d\r\ndrwxr-xr-x. 2 root root 4096 Jun 18 15:50 php.d\r\ndrwxr-xr-x. 2 root root 4096 Jun 18 15:50 php-zts.d\r\n-rw-r--r--. 1 root root 4023 May 23 08:26 php-fpm.conf\r\n-rw-r--r--. 1 root root 62221 May 23 08:26 php.ini<\/pre>\n<p>Next, adjust settings in your php.ini.<\/p>\n<h4>Fix your server&#8217;s Path Info behavior\u00a0\u00a0in php.ini (What is PATH_INFO\u00a0and why would\u00a0you care?)<\/h4>\n<p>Path info is information appended to\u00a0the URI of a PHP script. Path info, generally speaking, starts with a forward slash and\u00a0ends before the query arguments that begin\u00a0with a question mark (?).<\/p>\n<pre class=\"lang:sh decode:true\">http:\/\/www.domain.com\/path\/to\/a\/php-script.php\/PATH_INFO?query_args=foo<\/pre>\n<p>With cgi.fix_pathinfo enabled, PHP can tell\u00a0<code>PATH_INFO<\/code> and <code>SCRIPT_FILENAME<\/code>\u00a0apart (the latter one defined as the script file name prefixed with the document root).\u00a0<strong>This behavior opens up a security vulnerability<\/strong>, however: when calling a non-existent PHP script that&#8217;s appended to an image file, it is possible to execute malicious code in that file. (Since image files\u00a0can contain arbitrary code, it is possible \u00a0for a malicious user to craft an image that contains valid PHP that may be executed when the attacker exploits PATH_INFO.)\u00a0An example URI with that effect could look something like this:<\/p>\n<pre class=\"lang:sh decode:true\">http:\/\/www.domain.com\/malicious.jpg\/nonexistent.php<\/pre>\n<p>Since\u00a0nonexistent.php cannot be found in the file system, while\u00a0malicious.jpg is found in the location stipulated by the URI, nonexistent.php is deemed to be PATH_INFO and malicious.jpg it is considered a script, and executed.<\/p>\n<p>A partial fix to the\u00a0problem consists of\u00a0disallowing the execution of anything that does not end in .php. Recent versions of PHP-FPM set the default correctly, but it never hurts to verify.<\/p>\n<p>To confirm\u00a0that\u00a0the version installed on your system is using correct defaults, open the configuration file www.conf (and its equivalent for each of the other pools you configured) on your system and\u00a0find a\u00a0parameter named\u00a0security.limit_extensions. The default value in PHP 7 is:<\/p>\n<pre class=\"lang:sh decode:true\">security.limit_extensions = .php<\/pre>\n<p>Should you come across some compatibility issues, you may need to adjust this setting, for example:<\/p>\n<pre class=\"lang:sh decode:true\">security.limit_extensions = .php7<\/pre>\n<p>Even disallowing the execution of anything that does not end in .php is not completely fool-proof. For this very reason, the makers of NGINX recommend you set the\u00a0cgi.fix_pathinfo parameter in your php.ini to 0:<\/p>\n<pre class=\"lang:sh decode:true \">cgi.fix_pathinfo=0<\/pre>\n<p>With this option set, <strong>the PHP interpreter is only allowed to execute the script specified literally by the\u00a0URI, not any variations of it.<\/strong><\/p>\n<p>Tip: if your configuration changes don&#8217;t reflect in your browser, clear caches (browser and DNS resolver), or change the browser.<\/p>\n<h4>Configure the initial pool<\/h4>\n<p>In the same directory as php.ini, you will find\u00a0the directory php-fpm.d and in it the file <strong>www.conf<\/strong>. This file sets up the initial pool.<\/p>\n<p>HINT: If you are not sure how to find the location, execute<\/p>\n<pre>systemctl status php-fpm | \u00a0grep \"master process\"<\/pre>\n<p>in the command line and look for the location of the php-fpm master process.<\/p>\n<figure id=\"attachment_2311\" aria-describedby=\"caption-attachment-2311\" style=\"width: 591px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/php-fpm_master_process\/\" rel=\"attachment wp-att-2311\"><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-2311\" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/php-fpm_master_process-1024x71.png\" alt=\"php-fpm master process\" width=\"591\" height=\"41\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/php-fpm_master_process-1024x71.png 1024w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/php-fpm_master_process-300x21.png 300w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/php-fpm_master_process-768x53.png 768w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/php-fpm_master_process-600x42.png 600w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/php-fpm_master_process.png 1192w\" sizes=\"(max-width: 591px) 100vw, 591px\" \/><\/a><figcaption id=\"caption-attachment-2311\" class=\"wp-caption-text\">php-fpm master process<\/figcaption><\/figure>\n<p>The file you are looking for is located at the same location, inside of the <strong>php-fpm.d<\/strong> directory.<\/p>\n<p>Open it in a text editor of your choice. Find these two lines:<\/p>\n<pre class=\"lang:sh decode:true\">user = apache\r\ngroup = apache<\/pre>\n<p>and change them to:<\/p>\n<pre class=\"lang:sh decode:true\">user = nginx\r\ngroup = nginx\r\n<\/pre>\n<p>This setting necessitates\u00a0that nginx be the <strong>owner<\/strong> of the web server document directory and its contents; otherwise, the web application won&#8217;t be able to change files (membership in\u00a0the group should <strong>not<\/strong> be sufficient for write access! inside of the web server document directory). It is a good practice to create separate Unix\/Linux users and assign them ownership of their respective website directories (see section <em>Adding pools for multiple website owners<\/em> below for details on how to do this).<\/p>\n<p>FastCGI requests between NGINX and php-fpm can be passed back and forth either with the help of a Unix socket or via TCP\/IP; sockets are slightly faster but a lot less scaleable than TCP\/IP connections. If you want to use a Unix socket, you&#8217;ll have to uncomment the lines<\/p>\n<pre class=\"lang:sh decode:true \">;listen.owner = nobody\r\n;listen.group = nobody<\/pre>\n<p>(otherwise, leave them unchanged), and also adjust the corresponding setting in your site&#8217;s config file (otherwise, don&#8217;t touch the defaults).<\/p>\n<h4>Adding pools for multiple website owners<\/h4>\n<p>If you want your PHP application(s) to be able\u00a0to write to a website&#8217;s\u00a0document directory, PHP must spawn a\u00a0pool for this application\u00a0with the permissions of the owner of that\u00a0document directory (a\u00a0web server document\u00a0directory and its contents must not be writable by the group!).<\/p>\n<p>For php-fpm to initialize an\u00a0additional\u00a0pool, create a\u00a0copy of\u00a0the www.conf file:<\/p>\n<pre class=\"\">cp www.conf www.website1.tld.conf<\/pre>\n<p>rename the pool it contains:<\/p>\n<pre><del>[www]<\/del> [websiteowner1]<\/pre>\n<p>If you are using TCP sockets, assign the new pool a higher port number for the TCP socket:<\/p>\n<pre>listen = 127.0.0.1:9009<\/pre>\n<p>Change the user and group as follows:<\/p>\n<pre class=\"\">user = websiteowner1\r\ngroup = nginx<\/pre>\n<p>In the NGINX configuration file of the same website, remember to adjust the port number to the exact same port its pool will be using.<\/p>\n<p>After that, restart both services (php-fpm and nginx).<\/p>\n<p>Correct the SELinux labels on the configuration file of the newly created pool:<\/p>\n<pre>chcon -R -v system_u:object_r:etc_t:s0\u00a0www.websitename1.com.conf\r\nrestorecon www.websitename1.com.conf<\/pre>\n<p>If you encounter any other problems, they are most likely due to a misconfiguration of SELinux (see this post on <a href=\"https:\/\/cloudinsidr.com\/content\/troubleshooting-php-7-tcp-sockets-with-selinux-on-centos-7-rhelfedora\/\">troubleshooting SELinux for TCP sockets<\/a>).<\/p>\n<h2>Set up MariaDB (a replacement for MySQL)<\/h2>\n<p>MariaDB 5.x is drop-in compatible with MySQL. Having said that, you may want to remove MySQL if it is installed on your system, and use the 10.x branch of MariaDB (or above).<\/p>\n<h3>Add the official repository<\/h3>\n<p>Create a new text file containing the configuration of the official\u00a0MariDB repository for your system, for example for CentOS 7:<\/p>\n<pre class=\"\">[mariadb]\r\nname = MariaDB\r\nbaseurl = http:\/\/yum.mariadb.org\/10.1\/centos7-amd64\r\ngpgkey=https:\/\/yum.mariadb.org\/RPM-GPG-KEY-MariaDB\r\ngpgcheck=1<\/pre>\n<p>and save it in this directory:<\/p>\n<pre class=\"lang:sh decode:true\">\/etc\/yum.repos.d\/<\/pre>\n<h3>Install MariaDB<\/h3>\n<p>Install MariaDB using:<\/p>\n<pre class=\"fixed\">yum install MariaDB-server MariaDB-client<\/pre>\n<p>On Fedora:<\/p>\n<pre>dnf install mariadb mariadb-server<\/pre>\n<p>Unless you already\u00a0have the MariaDB GPG Signing key on your system, YUM will prompt you to install it after downloading the packages and\u00a0before installing them. Manual installation of the GPG key using the rpm takes this oneliner:<\/p>\n<pre class=\"lang:sh decode:true\">sudo rpm --import https:\/\/yum.mariadb.org\/RPM-GPG-KEY-MariaDB<\/pre>\n<h3>Start MariaDB<\/h3>\n<p>Start MariaDB (once):<\/p>\n<pre class=\"lang:sh decode:true \">systemctl start mariadb<\/pre>\n<p>Enable automatic launch of MariaDB on system startup:<\/p>\n<pre class=\"lang:sh decode:true\">systemctl enable mariadb<\/pre>\n<p>Verify if MariaDB is running:<\/p>\n<pre class=\"lang:sh decode:true\">systemctl status mariadb<\/pre>\n<h4>Secure your installation immediately<\/h4>\n<p>Securing your installation\u00a0of MariaDB works just the same as for MySQL and begins with the command:<\/p>\n<pre class=\"lang:sh decode:true \">mysql_secure_installation<\/pre>\n<p>Follow the onscreen instructions.<\/p>\n<p>If you are seeing this error:<\/p>\n<pre>YourPHP installation appears to be missing the MySQL extension which is required by WordPress.<\/pre>\n<p>you need to install the extension:<\/p>\n<pre>dnf install php-mysql<\/pre>\n<h3>Set up\u00a0WordPress (or another CMS of your choice)<\/h3>\n<p>Download your CMS with wget to a temp directory (grab the download link from the download page of your CMS). Unzip the archive and copy its contents into the web server document directory for your intended website.<\/p>\n<p>In order for the CMS to access your database, you need an unprivileged database user (never the database root!) and the database. The CMS system has a way to store this information for future access. Follow the setup instructions. For WordPress, the installation is described below but the principle is always more or less the same.<\/p>\n<h5>Create an unprivileged database user and a database<\/h5>\n<p>To create a database user and\u00a0the database in MySQL or MariaDB, you can use the mysql\u00a0client in the command line. Connect using:<\/p>\n<pre class=\"lang:mysql decode:true \">mysql -u root -p<\/pre>\n<p>Enter the root password. Once you are signed in and greeted with the MySQL prompt, create a new user and the database:<\/p>\n<pre class=\"lang:mysql decode:true\">CREATE USER 'dbuser'@'localhost';\r\n\r\nselect * from mysql.user;\r\n\r\ncreate database db_name;\r\n\r\nGRANT ALL PRIVILEGES ON db_name.* TO dbuser@localhost IDENTIFIED BY 'mysqldbuserpassword';<\/pre>\n<p>As soon as you create a new database, you have to IMMEDIATELY grant all privileges on it to the mysql root:<\/p>\n<pre class=\"lang:mysql decode:true\">GRANT ALL PRIVILEGES ON *.* TO root@'::1' IDENTIFIED BY 'rootsownpassword' WITH GRANT OPTION;\r\nSET PASSWORD FOR 'root'@'::1' = PASSWORD('rootsownpassword');<\/pre>\n<p>To verify the changes, use these commands:<\/p>\n<pre class=\"lang:sh decode:true\">SHOW GRANTS FOR 'root'@'localhost';\r\nSHOW GRANTS FOR 'Info_wrdp1'@'localhost';<\/pre>\n<p>Now you can flush privileges and exit:<\/p>\n<pre><span class=\"lang:sh decode:true crayon-inline\">flush privileges; exit;<\/span><\/pre>\n<h5>Enter database access credentials of the unprivileged user into the WP configuratIon<\/h5>\n<p>If you are using WordPress, copy the file wp-config-sample.php to its intended location:<\/p>\n<pre class=\"lang:sh decode:true\">cp\u00a0wp-config-sample.php\u00a0wp-config.php<\/pre>\n<p>Next, open it in a text editor of your choice.<\/p>\n<p>Find the lines that define the four parameters and make adjustments so that the parameters&#8217;s values match the settings in mysql:<\/p>\n<pre class=\"lang:sh decode:true\">\/** The name of the database for WordPress *\/\r\ndefine('DB_NAME', 'db_name');\r\n\r\n\/** MySQL database username *\/\r\ndefine('DB_USER', 'dbuser');\r\n\r\n\/** MySQL database password *\/\r\ndefine('DB_PASSWORD', 'mysqldbuserpassword');\r\n\r\n\/** MySQL hostname *\/\r\ndefine('DB_HOST', 'localhost');<\/pre>\n<p>Also, make sure you follow the instructions detailed\u00a0in the section\u00a0Authentication Unique Keys and Salts in wp-config.php.<\/p>\n<p>Save the file.<\/p>\n<h4>Access your WordPress installation in a web browser to create an admin account<\/h4>\n<p>Next, navigate to your WordPress installation in a web browser of your choice in order to set up the access of the first WordPress administrator to the administrative backend of your CMS. Do NOT enter here any user credentials that you have ever previously used: this is NOT a Unix\/Linux system user and NOT a mysql user. This is a new administrator of the CMS that will only ever access it in the web browser.<\/p>\n<p>If you decide to use a different\u00a0WordPress Database Table prefix than the default, make sure that you replace the default (wp_) with\u00a0the new\u00a0value\u00a0in wp-config.php in a\u00a0line that looks like this:<\/p>\n<pre class=\"lang:sh decode:true\">$table_prefix \u00a0= 'wp_';<\/pre>\n<h4>Allow write\u00a0operations for updates and plug-in installs: running WordPress without a need for (S)FTP access<\/h4>\n<p>In the\u00a0default configuration, WordPress is\u00a0unable to\u00a0upload updates or anything else for that matter\u00a0without FTP\/FTPS credentials. Since FTP can present a security vulnerability,\u00a0you may want to opt out of this entirely and either install a plug-in for key pair based authentication or allow for direct uploads via PHP.<\/p>\n<figure id=\"attachment_582\" aria-describedby=\"caption-attachment-582\" style=\"width: 760px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/wordpress_sftp\/\" target=\"_blank\" rel=\"attachment noopener wp-att-582\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-582\" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/WordPress_SFTP.png\" alt=\"WordPress: FTP or SFTP connection information\" width=\"760\" height=\"404\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/WordPress_SFTP.png 986w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/WordPress_SFTP-600x319.png 600w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/WordPress_SFTP-300x159.png 300w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/WordPress_SFTP-768x408.png 768w\" sizes=\"(max-width: 760px) 100vw, 760px\" \/><\/a><figcaption id=\"caption-attachment-582\" class=\"wp-caption-text\">WordPress: FTP or SFTP connection information<\/figcaption><\/figure>\n<p>In order to bypass the default request for FTP(S) credentials, open wp-config.php and enter this line:<\/p>\n<pre class=\"lang:sh decode:true \">define('FS_METHOD','direct');<\/pre>\n<p>right before the editable section ends with this line:<\/p>\n<pre class=\"lang:sh decode:true \">\/* That's all, stop editing! Happy blogging. *\/<\/pre>\n<p>Whether this setting immediately\u00a0reflects\u00a0in the behavior of WordPress depends\u00a0on whether\u00a0your file system access privileges and the SELinux security contexts or relevant files and directories\u00a0are set correctly. This is the next thing you should tackle (see below).<\/p>\n<h3>Setting SELinux security context for web server document directories of NGINX<\/h3>\n<p>Even after\u00a0you have adjusted the\u00a0ownership of files and directories within the web server root and set correct access permissions\u00a0on the entire web server\u00a0directory tree and its contents, NGINX may\u00a0still refusing to serve files with the 403 excuse (access denied).<\/p>\n<figure id=\"attachment_456\" aria-describedby=\"caption-attachment-456\" style=\"width: 662px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/403_forbidden\/\" rel=\"attachment wp-att-456\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-456 size-full\" src=\"https:\/\/cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/403_forbidden.jpg\" alt=\"403 forbidden: the NGINX error page\" width=\"662\" height=\"148\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/403_forbidden.jpg 662w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/403_forbidden-600x134.jpg 600w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/403_forbidden-300x67.jpg 300w\" sizes=\"(max-width: 662px) 100vw, 662px\" \/><\/a><figcaption id=\"caption-attachment-456\" class=\"wp-caption-text\">403 forbidden: the dreaded NGINX error page points to wrong permissions or an inappropriate SELinux security context<\/figcaption><\/figure>\n<p>Or, in a more subtle but no less unnerving scenario, your web application may fail to perform activities that depend on its ability to create or write to directories, such as installing or updating plug-ins by WordPress.<\/p>\n<figure id=\"attachment_466\" aria-describedby=\"caption-attachment-466\" style=\"width: 760px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/permissions_conflict_plugins\/\" target=\"_blank\" rel=\"attachment noopener wp-att-466\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-466\" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/permissions_conflict_plugins.png\" alt=\"An error occurred while updating Akismet: Could not create directory... (courtesy of SELinux, of course)\" width=\"760\" height=\"398\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/permissions_conflict_plugins.png 908w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/permissions_conflict_plugins-600x314.png 600w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/permissions_conflict_plugins-300x157.png 300w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/permissions_conflict_plugins-768x402.png 768w\" sizes=\"(max-width: 760px) 100vw, 760px\" \/><\/a><figcaption id=\"caption-attachment-466\" class=\"wp-caption-text\">An error occurred while updating Akismet: Could not create directory&#8230; (courtesy of SELinux, of course)<\/figcaption><\/figure>\n<p>If either\u00a0is the case, you should look into adjusting alternative access methods such as SELinux.<\/p>\n<p>(Nothing else, not even adding<\/p>\n<pre class=\"lang:sh decode:true \">define('FS_METHOD','direct');<\/pre>\n<p>to wp-config.php will fix the problem unless SELinux context labels are correctly set.) Still need convincing? A look at the audit log can help you find the culprit:<\/p>\n<pre class=\"lang:sh decode:true \">tail \/var\/log\/audit\/audit.log<\/pre>\n<p>Simply deactivating SELinux is not the wisest course of action. SELinux is not the evil it&#8217;s made out to be. Don&#8217;t deactivate it, tame it.<\/p>\n<p>In order to understand what&#8217;s at stake here it helps being able to view the SELinux labels on files and directories.<\/p>\n<h4>List files and directories with their respective security context<\/h4>\n<p>For a full directory listing that includes SELinux security context, use the -Z option with ls, for example:<\/p>\n<pre class=\"lang:sh decode:true\">ls -laZ\u00a0\/var\/www\/www.yourwebstite.tld\/<\/pre>\n<p>where\u00a0www.yourwebstite.tld is the web server root directory.<\/p>\n<h4>View the current SELinux security context for the NGINX master and worker processes while\u00a0running<\/h4>\n<p>In order to view\u00a0the\u00a0SELinux context for the master and worker processes of NGINX, use the following\u00a0command:<\/p>\n<pre class=\"lang:sh decode:true\">ps -efZ | grep 'nginx'<\/pre>\n<p>The command reveals the user NGINX is running as. It selects &#8216;nginx&#8217; from all running processes (option -e is interchangeable with -A for &#8216;all&#8217;) and\u00a0requests full-format listing (-f ;\u00a0use option -F for extra full options).<\/p>\n<figure id=\"attachment_452\" aria-describedby=\"caption-attachment-452\" style=\"width: 760px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/nginx_in_selinux_context\/\" target=\"_blank\" rel=\"attachment noopener wp-att-452\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-452\" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_in_SELinux_context-1024x78.png\" alt=\"View current SELinux security context of NGINX \" width=\"760\" height=\"58\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_in_SELinux_context-1024x78.png 1024w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_in_SELinux_context-600x46.png 600w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_in_SELinux_context-300x23.png 300w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_in_SELinux_context-768x59.png 768w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/nginx_in_SELinux_context.png 1453w\" sizes=\"(max-width: 760px) 100vw, 760px\" \/><\/a><figcaption id=\"caption-attachment-452\" class=\"wp-caption-text\">View current SELinux security context of NGINX<\/figcaption><\/figure>\n<p>To verify which adjustments of\u00a0the SELinux security context are required to conform to the defaults, run restorecon without writing any changes (-n):<\/p>\n<pre class=\"lang:sh decode:true\">restorecon -RFv -n \/var\/www\/<\/pre>\n<p>The\u00a0verbose output of this command should give you an idea as of what&#8217;s going to happen when you omit the -n flag.<\/p>\n<p>In order to apply SELinux defaults\u00a0that are inherited from the parent directory \/var\/www, use the above command without the -n option:<\/p>\n<pre class=\"lang:sh decode:true\">restorecon -RFv \/var\/www\/<\/pre>\n<p>The defaults will enforce SELinux security to the point of making updates, uploads, and plug-in installations all but impossible. No amount of modifying the ownerships and privileges the old-fashioned way (chmod\/chown\/umask etc.) will get your CMS system to work properly, until you apply some corrections withing SELinux security context of the relevant directory trees.<\/p>\n<p>For WordPress, adjust the SELinux label to establish proper security context on the website directory:<\/p>\n<pre class=\"lang:sh decode:true\">chcon system_u:object_r:httpd_sys_content_t:s0 \/var\/www\/www.website1.tld<\/pre>\n<p>Use this command on the WordPress installation directory to allow write access to its contents (-R):<\/p>\n<pre class=\"\">chcon -R system_u:object_r:httpd_sys_rw_content_t:s0 www.website1.tld\/*<\/pre>\n<p>You need to apply the above label to the entire directory tree of your web application (WordPress in the above example) in order to make it writeable.<\/p>\n<p>After executing the above\u00a0command, verify that your web application is working correctly. In order to make this change permanent, use:<\/p>\n<pre class=\"lang:sh decode:true\">semanage fcontext -a -t httpd_sys_rw_content_t  \"\/var\/www\/www.website1root.tld(\/.*)?\"<\/pre>\n<p>The above command will add a rule to apply the specified context change permanently\u00a0<span style=\"line-height: 1.6471;\">so that the command restorecon (which you may issue on the above directory in the future):<\/span><\/p>\n<pre class=\"lang:sh decode:true\">restorecon -Rv\u00a0\/var\/www\/www.website1root.tld\/pathto\/wp-content\/<\/pre>\n<p>can use the new policy just the way you intended. You can view the rule (but not edit it directly) using this command:<\/p>\n<pre class=\"\">cat \/etc\/selinux\/targeted\/contexts\/files\/file_contexts.local\r\n<\/pre>\n<p>Should you ever need to remove a rule, use the -d option (-d for delete):<\/p>\n<pre class=\"\">semanage fcontext -d \"\/var\/www\/www.website1.tld\/pathtoremoverulefor(\/.*)?\"<\/pre>\n<p>Your rule will look something like this (don&#8217;t edit directly!):<\/p>\n<pre class=\"\">\/var\/www\/www.website1.tld(\/.*)? \u00a0 \u00a0system_u:object_r:httpd_sys_rw_content_t:s0<\/pre>\n<p>IMPORTANT: Always use absolute paths when changing\u00a0SELinux contexts!<\/p>\n<p>Once you rein in\u00a0SELinux, your web application should be up and running, and able to perform auto-updates via PHP.<\/p>\n<figure id=\"attachment_467\" aria-describedby=\"caption-attachment-467\" style=\"width: 760px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/selinux_works_with_uploads\/\" target=\"_blank\" rel=\"attachment noopener wp-att-467\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-467\" src=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/SELinux_works_with_uploads.png\" alt=\"Akismet correctly updated after adjusting SELinux security context label on the wp-content\/ directory\" width=\"760\" height=\"327\" srcset=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/SELinux_works_with_uploads.png 896w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/SELinux_works_with_uploads-600x258.png 600w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/SELinux_works_with_uploads-300x129.png 300w, https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/12\/SELinux_works_with_uploads-768x331.png 768w\" sizes=\"(max-width: 760px) 100vw, 760px\" \/><\/a><figcaption id=\"caption-attachment-467\" class=\"wp-caption-text\">Akismet correctly updated after adjusting Unix permissions and SELinux security context labels on the wp-content\/ directory<\/figcaption><\/figure>\n<p>For <a href=\"https:\/\/cloudinsidr.com\/content\/understanding-and-settingchanging-access-privileges-on-unixlinux-files-and-directories-mode-bits-and-alternative-access-methods-explained\/\">a more detailed explanation of Unix\/GNU Linux permissions and alternative access methods<\/a>, see <a href=\"https:\/\/cloudinsidr.com\/content\/understanding-and-settingchanging-access-privileges-on-unixlinux-files-and-directories-mode-bits-and-alternative-access-methods-explained\/\">this post<\/a>.<\/p>\n<p>If SELinux is giving you trouble, see these troubleshooting posts:<\/p>\n<ul>\n<li><a href=\"https:\/\/cloudinsidr.com\/content\/troubleshooting-php-7-tcp-sockets-with-selinux-on-centos-7-rhelfedora\/\">Troubleshooting PHP 7 TCP Sockets with SELinux on CentOS 7 (RHEL\/Fedora<\/a>)<\/li>\n<li><a href=\"https:\/\/cloudinsidr.com\/content\/troubleshooting-php-7-tcp-sockets-with-selinux-on-centos-7-rhelfedora\/\">Tip of The Day: Investigating SELinux Security Contexts To Adjust SELinux Labels on Your Linux System<\/a><\/li>\n<\/ul>\n<p>For more practical tips and configuration howtos, subscribe to our newsletter, it&#8217;s free!<\/p>\n<p>[wysija_form id=&#8221;1&#8243;]<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The setup of the LEMP stack (NGINX, MariaDB or MySQL, and PHP) has many caveats. They can impact both performance and security. Here is how to LEMP (not limp along!).<\/p>\n","protected":false},"author":101012,"featured_media":76,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_genesis_hide_title":false,"_genesis_hide_breadcrumbs":false,"_genesis_hide_singular_image":false,"_genesis_hide_footer_widgets":false,"_genesis_custom_body_class":"","_genesis_custom_post_class":"","_genesis_layout":"","footnotes":""},"categories":[17,33,143,109,154,6],"tags":[81,82,7,25,50,27,83,70],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>How to set up the LEMP stack: Linux, NGINX, MariaDB or MySQL, PHP<\/title>\n<meta name=\"robots\" content=\"index, follow\" \/>\n<meta name=\"googlebot\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta name=\"bingbot\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to set up the LEMP stack: Linux, NGINX, MariaDB or MySQL, PHP\" \/>\n<meta property=\"og:description\" content=\"The setup of the LEMP stack (NGINX, MariaDB or MySQL, and PHP) has many caveats. They can impact both performance and security. Here is how to LEMP (not limp along!).\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/\" \/>\n<meta property=\"og:site_name\" content=\"CloudInsidr\" \/>\n<meta property=\"article:published_time\" content=\"2015-12-07T14:07:22+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-03-21T13:48:16+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/11\/cloudinsidr_logo_900px-wide.png\" \/>\n\t<meta property=\"og:image:width\" content=\"900\" \/>\n\t<meta property=\"og:image:height\" content=\"326\" \/>\n<meta name=\"twitter:card\" content=\"summary\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.cloudinsidr.com\/content\/#website\",\"url\":\"https:\/\/www.cloudinsidr.com\/content\/\",\"name\":\"CloudInsidr\",\"description\":\"Cyber security, infotech\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/www.cloudinsidr.com\/content\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/www.cloudinsidr.com\/content\/wp-content\/uploads\/2015\/11\/cloudinsidr_logo_900px-wide.png\",\"width\":900,\"height\":326,\"caption\":\"cloudinsidr.com logo (900px wide)\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/#webpage\",\"url\":\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/\",\"name\":\"How to set up the LEMP stack: Linux, NGINX, MariaDB or MySQL, PHP\",\"isPartOf\":{\"@id\":\"https:\/\/www.cloudinsidr.com\/content\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/#primaryimage\"},\"datePublished\":\"2015-12-07T14:07:22+00:00\",\"dateModified\":\"2022-03-21T13:48:16+00:00\",\"author\":{\"@id\":\"https:\/\/www.cloudinsidr.com\/content\/#\/schema\/person\/73723b2da71b6d515d17ca593ea5dc68\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.cloudinsidr.com\/content\/lemp-how-to-set-up-nginx-with-mariadbmysql-and-php-7-x-on-centos-7-rhelfedora\/\"]}]},{\"@type\":[\"Person\"],\"@id\":\"https:\/\/www.cloudinsidr.com\/content\/#\/schema\/person\/73723b2da71b6d515d17ca593ea5dc68\",\"name\":\"Filipe Martins\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/www.cloudinsidr.com\/content\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/dbac033b4d26da8ca1fbde233e49c8dc?s=96&d=mm&r=g\",\"caption\":\"Filipe Martins\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/posts\/382"}],"collection":[{"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/users\/101012"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/comments?post=382"}],"version-history":[{"count":129,"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/posts\/382\/revisions"}],"predecessor-version":[{"id":2823,"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/posts\/382\/revisions\/2823"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/media\/76"}],"wp:attachment":[{"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/media?parent=382"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/categories?post=382"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cloudinsidr.com\/content\/wp-json\/wp\/v2\/tags?post=382"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}