What's more?

Last updated on
19 March 2025

This documentation is deprecated.

There might be additional server setup you would like to replicate in your development environment.

Password protect the staging server but not local or production

Usually, you don't want your staging server to be publically available, apart from hiding it from prying eyes mainly to avoid it being indexed by search engines. This can be accomplished with Htaccess password protection (aka shielding). However, you don't want Htaccess password protection to kick in on your local or production environment. This can be easily accomplished by making an addition to the .htaccess file in the Drupal root. There is no need for additional Drupal modules, like Shield.

Next, you have to generate a user with a hashed password and put it in a .htpasswd file in the same folder. Remember that you use the full path to it in the .htaccess file, for example, /var/www/html/staging.domain.com/web/.htpasswd.

Protect the production domain by allowing admin operations only through a shielded sub-domain

With the Limit Domain Access By Role module, you can whitelist a shielded technical sub-domain (e.g. your-client.your-agency.com) to allow admin roles to interact only there. It points to the same folder as the production domain (e.g. your-client.com), so any admin operations affect the same database, but direct access to the production domain is disallowed for admin roles.
This way, a malicious user that somehow manages to get an admin role assigned or obtain credentials of user #1, will not be able to login with it on the production domain. Even if they know the URL of the technical sub-domain, that one requires then an additional Htaccess password, one of the strongest methods of protection.

To implement, just swap in the linked instructions in the section above the suggested staging domain with the used technical sub-domain and enable the Limit Domain Access By Role module. Additionally, it gives the advantage to be able to test if the technical domain is still running when the production domain is down, indicating probably the DNS server is the culprit.

More info:
Would the implementation of separation of frontend and backend increase website security in any noticeable or reasonable way? - Information Security Stack Exchange

Additional security

Website security often consists of a series of measures. If one fails, the next one will be there. The more hurdles there are, the better. Even if some can be circumvented, they might still make it harder to get in, especially for automated malicious scripts that rely on a common file location or set up. Apart from allowing admin operations only through a shielded sub-domain as described above, there are other measures to "disorientate" potential malicious visitors:

  • Change the location from the commonly used web directory path such as /var/www/html to something less predictable.
  • Instead of using 'admin' as the username of user #1, use another name, even just changing one character like 'atmin'. Obviously, its profile page should not be accessible and you should not use the superuser to create content either, to avoid the username being exposed as the author.
  • On the production site always use composer install --no-dev, so that unnecessary and vulnerable packages as PHPUnit are not available as attacking points.
  • Always secure the files on your website properly. Why (and how to do it in Drupal) | Lucius.

Run cron locally

Set up cron to run locally every five minutes:

  • Go to admin/config/system/cron and copy the URL to run cron from outside the site.
  • From the terminal run: crontab -e
  • Add as the last line: */5 * * * * curl -s -o /dev/null "[paste the URL copied previously]". See crontab every 5 minutes.
  • Save.
  • Go back to admin/config/system/cron and refresh the page regularly to see if cron runs every five minutes (might not be exact).
  • Remember that Ultimate Cron extends Drupal standard cron, allowing fine-grained control over the exact interval of each task. Go to admin/config/system/cron/jobs to check.

The settings.local.php file

Use a settings.local.php file to override or add configuration settings on your development environment.

A Drupal 7 example:

<?php
$conf['stage_file_proxy_hotlink'] = TRUE; // To use please enable the stage_file_proxy module to hotlink broken images to the live site.
$conf['stage_file_proxy_origin'] = 'https://www.example.com'; // The live site to hotlink from.
$conf['stage_file_proxy_origin_dir'] = 'sites/default/public_files'; // The path to hotlink from.

$conf['preprocess_css'] = 0; // Switch off CSS aggregation.
$conf['preprocess_js'] = 0; // Switch off JS aggregation.

$conf['theme_debug'] = TRUE; // See template suggestions and the locations of template files in the HTML markup.

$conf['error_level'] = 2; // Error messages to display.

ini_set('max_execution_time', 60); // To adjust PHP execution time.
ini_set('memory_limit', 64M); // To adjust PHP memory limit

$databases['default']['default']['init_commands'] = array(
  'isolation' => "SET SESSION tx_isolation='READ-COMMITTED'"
); // avoid db deadlocks. Ref: https://www.drupal.org/node/1952972 

// Override of a path that doesn't exist on local.
$conf['file_private_path'] = '';

For Drupal 8 (settings.local.php) you would use:

<?php
$config['system.performance']['css']['preprocess'] = 0; // Switch off CSS aggregation.

See Configuration override system | Drupal.org.

The best starting point is to copy sites/example.settings.local.php to sites/default/settings.local.php and then uncomment the last part of sites/default/settings.php to load the local development override configuration and then disable caching during development.

Multisite setup

See Multi-site - Sharing the same code base | Drupal 7 guide on Drupal.org

Note:

  • This document is based upon Drupal 7, but it's also valid for Drupal 8. There are some specifics for Drupal 8, when you read the full document you will come across them.
  • Drush commando for multisite e.g. to clear the cache of a specific domain:
    drush -l someotherdomain.localhost cr

Caching mechanism

Additional steps might be needed to make the cache work as on your production server like Memcache.

Example settings.local.php:

// Memcache settings.
$conf['cache_backends'][] = 'sites/all/modules/contrib/memcache/memcache.inc';
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
$conf['cache_default_class'] = 'MemCacheDrupal';
$conf['memcache_key_prefix'] = 'default';
$conf['lock_inc'] = 'sites/all/modules/contrib/memcache/memcache-lock.inc';
$conf['memcache_stampede_protection'] = TRUE;
$conf['memcache_servers'] = array(
        '127.0.0.1:11211' => 'default',
     );

If you have a server cluster handled via load balancer then the server version of local.settings.php might look like this in each server

// Memcache settings.
$conf['cache_backends'][] = 'sites/all/modules/contrib/memcache/memcache.inc';
$conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
$conf['cache_default_class'] = 'MemCacheDrupal';
$conf['memcache_key_prefix'] = 'default';
$conf['lock_inc'] = 'sites/all/modules/contrib/memcache/memcache-lock.inc';
$conf['memcache_stampede_protection'] = TRUE;
$conf['memcache_servers'] = array(
     'server1:11211' => 'default',
     'server2:11211' => 'default',
     'server3:11211' => 'default',
     'server4:11211' => 'default',
     );

Additional steps might be needed to make the search mechanism work as on your production server like Solr.

Alternatively: Install Solr 7 for Drupal 8 Search API on Ubuntu 16.04 (seems still valid for 18.04, open for discussion)

Example settings.local.php:

<?php
// Local Solr configuration.
$conf['search_api_override_mode'] = 'load';
$conf['search_api_override_servers'] = array(
  'solr_server' => array(
    'name' => 'My Solr Server (overriden)',
    'options' => array(
      'host' => 'localhost',
      'port' => '8983',
      'scheme' => 'http',
      'path' => '/solr',
      'http_user' => '',
      'http_pass' => '',
      'excerpt' => 0,
      'retrieve_data' => 0,
      'highlight_data' => 0,
      'http_method' => 'POST',
    ),
  ),
);

Re-index search (using Search API):

drush search-api:reset-tracker
drush search-api:index
drush cr

Consuming external services

You might need to add credentials to the settings.php file to be able to authenticate on external services. Furthermore, you might need additional packages like a SoapClient installation.

Example settings.local.php:

<?php
$conf['service_client_submit'] = array(
 'soap_service_endpoint' => 'https://example.com/some_service',
 'service_username' => 'some_username',
 'service_password' => 'some_password',
 );

Perform a PHP upgrade

Run the recommended version of PHP for your version of Drupal. That is unlikely to be possible if you run a mix of Drupal versions on the same server, for example, Drupal 7 and Drupal 8.  It is wise to keep separate servers for the web applications that you intend to upgrade quickly in the future and those that can wait for a while. This decision is likely based on:

  • complexity: Many or particular contrib modules and custom code make a Drupal version upgrade more time-consuming.
  • security: the amount and type of user interaction. Less user input from unknown users usually makes a web application less likely to be hacked. You can alleviate this by allowing admin operations only on a technical (sub)domain (admin.example.com) that is different from the URL of the publically available site (example.com). This mitigates XSS and cross-site request forgery vulnerabilities, the most commonly needed protection.
  • security: the type of data and uploaded files that are stored. If high sensitive user data is present, the consequence of a security breach is bigger. Data/file encryption and setting up the Private File System correctly can alleviate this.
  • security: the severity of a data loss. Having a frequent backup and restore system for both data and files can alleviate this. 

In preparation for a Drupal version upgrade, it is likely it has to be preceded by a PHP upgrade. Do that locally first and test. Document the followed steps to be able to replicate this on the remote servers.

An example of an upgrade from PHP 7.2 to 7.4

Based on Ubuntu with Apache and MySQL.

Used terminal commands

Check your current PHP version:

php -v

Check your current PHP extensions:

php -m

Install PHP 7.4 core: 

sudo add-apt-repository ppa:ondrej/php # Press enter to confirm.
sudo add-apt-repository ppa:ondrej/apache2 # Press enter to confirm.
sudo apt-get update
sudo apt install php7.4 php7.4-common php7.4-cli

Install the required extensions:

sudo apt install php7.4-curl php7.4-json php7.4-gd php7.4-mbstring php7.4-intl php7.4-mysql php7.4-dom php7.4-bcmath php7.4-bz2 php7.4-readline php7.4-zip

Deactivate the old PHP version and activate the new one:

sudo a2dismod php7.2 && sudo a2enmod php7.4

Optional: Allow for  PHP short tag (<?) besides a regular PHP tag (<?php):

sudo nano /etc/php/7.4/apache2/php.ini
short_open_tag = On # Change the following line from Off to On.

Check if the MySQL Improved Extension is enabled:

php -m | grep mysqli

Restart Apache:

sudo service apache2 restart
  • If you get any errors on Apache restart, you can debug with:

    systemctl status apache2.service

Check your new PHP version:

php -v

Check your new PHP extensions:

php -m

Run the website on the new PHP version to test

Clear the cache of the websites running on this server and visit them in the web browser to see if they run without hick-ups. Log in as a super admin and check the status report at admin/reports/status.

Access recent log messages (watchdog)

The log messages list can normally be accessed at admin/reports/dblog if the core dblog module is enabled. You can use drush watchdog:show in case you do not have admin access OR you get:
The website encountered an unexpected error. Please try again later.

Additional sources

Next: Know your tools >>

Help improve this page

Page status: Deprecated

You can: