LEMP Server setup for Drupal.

This is a tutorial to install LEMP with Drupal with OPCache, Varnish, and Apache Solr. This will result in a ready production website, covering 99.9% of Drupal based website needs.

1. Create an admin user.

$ adduser site_admin
$ usermod -a -G sudo,root,www-data site_admin

2. Install MariaDB.

MariaDB server is better than a standard MySQL for it’s optimizations, and must be installed early on to avoid conflicts.

$ apt install software-properties-common
$ apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
$ add-apt-repository 'deb [arch=amd64,i386,ppc64el] http://nyc2.mirrors.digitalocean.com/mariadb/repo/10.1/ubuntu xenial main'
$ apt update
$ apt install mariadb-server

Edit /etc/mysql/my.cnf, set innodb_buffer_pool_size = 512M

3. Install nginx.

$ echo "deb http://ppa.launchpad.net/nginx/stable/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/nginx-stable.list
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C300EE8C
$ sudo apt update
$ sudo apt install nginx

Edit /etc/nginx/nginx.conf, add under http section: client_max_body_size 256M;

4. Install PHP and libraries.

$ apt install php curl ssl-cert wwwconfig-common libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libjs-jquery libjs-sphinxdoc libjs-underscore liblua5.1-0 libmcrypt4 php-common php-gd php-gettext php-mbstring php-mcrypt php-mysql php-pear php-phpseclib php-tcpdf php-xml php-cli php-gd php-json php-mbstring php-mcrypt php-mysql php-opcache php-readline php-xml php-fpm php-bz2 php-zip php-imagick php-curl php-geoip php-mail php-oauth unzip php-zip redis-server php-redis

Set php-fpm user to current in /etc/php/7.0/fpm/pool.d/www.conf

Set php-fpm daemon in /etc/php/7.0/fpm/pool.d/www.conf
pm.max_children = 50

5. Install PHPMyAdmin.

When installing PHPMyAdmin we don’t want to pull apache with it. Prerequisites have been installed in the previous step.

$ sudo apt --no-install-recommends install phpmyadmin

6. Install Drush:

$ wget http://files.drush.org/drush.phar
$ chmod +x drush.phar
$ mv drush.phar /usr/bin/drush
$ drush init

Refer: http://docs.drush.org/en/master/install/

7. Install Git and git-flow:

$ apt install git
$ apt install git-flow

8. Install Composer.

$ curl -sS https://getcomposer.org/installer | php
$ chmod +x composer.phar
$ mv composer.phar /usr/bin/composer

Refer: http://symfony.com/doc/current/cookbook/composer.html

9. Install htop.

$ apt install htop

10. Install Fish.

$ apt install fish

Check where fish is installed before going on.

$ which fish
$ chsh -s /usr/bin/fish
$ curl -L https://github.com/oh-my-fish/oh-my-fish/raw/master/bin/install | fish
$ omf install agnoster

11. Tune PHP.

In /etc/php/7.0/fpm/php.ini file:

memory_limit = -1
post_max_size = 64M
upload_max_filesize = 64M
max_execution_time = 60
max_input_vars = 32000

In /etc/php/7.0/fpm/conf.d/10-opcache.ini file:

opcache.enable=1
opcache.revalidate_freq=0
opcache.validate_timestamps=0
opcache.max_accelerated_files=16000
opcache.memory_consumption=128
opcache.interned_strings_buffer=32
opcache.fast_shutdown=1

If using php5.6, set always_populate_raw_post_data = -1 in php.ini

12. Tune redis.

Drupal module.
https://www.drupal.org/project/redis

Configure:
Edit /etc/redis/redis.conf and set:

databases 1
maxmemory 1024mb
loglevel warning
maxmemory-policy allkeys-lru

D7: In settings.php:

$conf['redis_client_interface'] = 'PhpRedis'; // Can be "Predis".
$conf['redis_client_host'] = '127.0.0.1';
$conf['lock_inc'] = 'sites/all/modules/contrib/redis/redis.lock.inc';
$conf['path_inc'] = 'sites/all/modules/contrib/redis/redis.path.inc';
$conf['cache_backends'][] = 'sites/all/modules/contrib/redis/redis.autoload.inc';
$conf['cache_default_class'] = 'Redis_Cache';
$conf['cache_class_cache'] = 'Redis_Cache';
$conf['cache_class_cache_bootstrap'] = 'Redis_Cache';
$conf['cache_class_cache_menu'] = 'Redis_Cache';
$conf['cache_class_cache_block'] = 'Redis_Cache';
$conf['cache_class_cache_content'] = 'Redis_Cache';
$conf['cache_class_cache_filter'] = 'Redis_Cache';
$conf['cache_class_cache_form'] = 'Redis_Cache';
$conf['cache_class_cache_page'] = 'Redis_Cache';

D8: In settings.php:

$settings['redis.connection']['interface'] = 'PhpRedis';
$settings['redis.connection']['host'] = '127.0.0.1'; $settings['cache']['default'] = 'cache.backend.redis';
$settings['cache']['bins']['bootstrap'] = 'cache.backend.chainedfast';
$settings['cache']['bins']['discovery'] = 'cache.backend.chainedfast';
$settings['cache']['bins']['config'] = 'cache.backend.chainedfast';
$settings['container_yamls'][] = 'modules/redis/example.services.yml';

In services.yml:

$settings['container_yamls'][] = 'modules/redis/example.services.yml';

Run redis-cli info and verify it works.

13. Setup varnish.

Install varnish:

apt install varnish

Configure /etc/default/varnish file:

Set DAEMON_OPTS -a to listen to port 80.

Configure /etc/varnish/default.vcl:
https://www.drupal.org/docs/7/caching-to-improve-performance/varnish-4x-...

Edit the nginx virtualhost to listen to port 8080.

Edit /etc/varnish/secret. Set it to admin/config/development/varnish

Fix the systemd issue:

$ cp /lib/systemd/system/varnish.service /etc/systemd/system/

Edit /etc/systemd/system/varnish.service and set -a to :80. Reboot the server.

Install drupal module:
https://www.drupal.org/project/varnish

Add settings.php:

$conf['cache_backends'][] = 'sites/all/modules/contrib/varnish/varnish.cache.inc';
$conf['cache_class_cache_page'] = 'VarnishCache';
$conf['page_cache_invoke_hooks'] = FALSE;
$conf['omit_vary_cookie'] = TRUE;
$conf['reverse_proxy_header'] = 'HTTP_X_FORWARDED_FOR';
$conf['reverse_proxy_addresses'] = array('127.0.0.1');

14. Final preparations.

14a. Cron.

0 * * * *   idm     /usr/bin/curl -s http://localhost/cron.php?cron_key=hNkEIUmRyEQ7wTZxQHemFJyVh-2V1M4YBOsZ0RC2_vQ > /dev/null

14b. MX Record and DNS for www subdomain.

14c. Clean the apt archives.

$ apt clean

15. Install Apache Solr.

Install unzip (required for solr):

$ apt install unzip

Install Java:

$ apt install python-software-properties
$ apt install default-jdk

Install Solr:

$ cd ~
$ wget http://archive.apache.org/dist/lucene/solr/5.5.3/solr-5.5.3.tgz
$ tar xzf solr-5.5.3.tgz solr-5.5.3/bin/install_solr_service.sh --strip-components=2
$ sudo bash ./install_solr_service.sh solr-5.5.3.tgz

Check if Solr is running:

$ sudo service solr status

Check if Solr UI is running:
http://your_server_ip:8983/solr

15a Password-protect Solr

Edit /opt/solr/server/etc/jetty.xml
Add before the closing configure tag:

    <Call name="addBean">
      <Arg>
        <New class="org.eclipse.jetty.security.HashLoginService">
          <Set name="name">Solr Realm</Set>
          <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
          <Set name="refreshInterval">0</Set>
        </New>
      </Arg>
    </Call>

Edit /opt/solr/server/solr-webapp/webapp/WEB-INF/web.xml
Add before the closing web-app tag:

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Solr authenticated application</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>core1-role</role-name>
    </auth-constraint>
  </security-constraint>

  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>Solr Realm</realm-name>
  </login-config>

Create or edit /opt/solr/server/etc/realm.properties
Add the line:

admin: solr_password_here,core1-role.

Copy over the configs and create the core folder structure:

$ mkdir /opt/solr/server/solr/drupal
$ mkdir /opt/solr/server/solr/drupal/conf/
$ mkdir /opt/solr/server/solr/drupal/data/
$ ln -s /opt/solr/server/solr/drupal /var/solr/data/drupal
$ cp <search_api_module_path>/search_api_solr/solr-conf/5.x/*.* /opt/solr/server/solr/drupal/conf/
$ chown -R solr /opt/solr/server/solr/drupal

Restart Solr:

$ service solr restart

Create a core with name and folder of drupal in Solr UI.

16. Checkups.

  • Check www to non www redirect.
  • Check file system permissions.