Drupal Security Hardening — Updates, Patches and Security Best Practices
In this tutorial, you'll learn how to harden Drupal security: keeping core and modules updated, configuring secure settings.php and .htaccess files, setting up trusted host patterns, implementing two-factor authentication, and using security modules to protect your site.
What You'll Learn
- The Drupal Security Advisory Process and how to stay informed
- Updating core and modules via Composer
- Securing settings.php: credentials, hash salt, trusted host patterns, base_url
- File permissions: directories 755, files 644, sites/default strict
- .htaccess hardening: blocking file access, preventing PHP execution
- services.yml security: trusted_host_patterns, CORS configuration
- User login security: limiting login attempts
- Password policy: strong passwords, regular changes
- Two-factor authentication with the TFA module and Google Authenticator
- Security modules: Security Kit, Paranoia, Username Enumeration Prevention
- Drupal vulnerability scanner and regular security audits
- Securing cron with a cron key
- SSL/HTTPS configuration and mixed content prevention
Why It Matters
Drupal powers government websites, enterprise portals, and high-traffic platforms — making it a prime target for attackers. The Drupal security team releases advisories regularly, and unpatched sites are compromised within hours of a public disclosure. Security hardening is not optional. A single vulnerability in a contributed module can expose your entire database, including user credentials and sensitive content. Following security best practices protects your data, your users, and your reputation.
Real-World Use
A government agency running a Drupal site with citizen data must comply with security standards. They subscribe to Drupal security advisories, apply patches within 48 hours of release, restrict file permissions, enforce strong passwords with 2FA, and run monthly security audits. When the Drupal security team announced a critical remote code execution vulnerability (SA-CORE-2023-001), the agency patched within hours because their update process was automated and tested. Unpatched sites running the same vulnerability were compromised within 24 hours.
Learning Path
flowchart LR A[User Management] --> B[Security Hardening] B --> C[Updates and Patches] C --> D[File Permissions] D --> E[.htaccess and settings.php] E --> F[2FA and Password Policy] F --> G[Security Modules] G --> H[Monitoring and Audits] H --> I[Go-Live Checklist]
Understanding the Drupal Security Advisory Process
The Drupal security team is a volunteer group that reviews code and responds to vulnerability reports. When a security issue is confirmed, they release a Security Advisory (SA).
Security Advisory Types
- SA-CORE: Core vulnerabilities affecting Drupal itself
- SA-CONTRIB: Contributed module or theme vulnerabilities
- SA-PUBLIC: Public service announcements
Each advisory includes:
- Severity level (Critical, Highly critical, Moderate, Less critical)
- Affected versions
- Fixed versions
- Description of the vulnerability
- Mitigation steps if a patch is not yet available
Staying Informed
Subscribe to the security newsletter at Drupal.org and follow the @drupalsecurity account. Configure your site's Update Status module to email you when updates are available.
Updating Core and Modules
Keeping Drupal updated is your primary defense against known vulnerabilities.
Updating with Composer
# Check for available updates
composer outdated drupal/*
# Update Drupal core to the latest version
composer update drupal/core-recommended --with-all-dependencies
# Update a specific contributed module
composer update drupal/pathauto --with-all-dependencies
# Update all Drupal packages
composer update drupal/* --with-all-dependencies
Running Database Updates
After updating code, always run database updates:
# Run database updates via Drush
drush updatedb
# Or via the web interface
# Navigate to /update.php after core update
# Full update workflow
# 1. Put site in maintenance mode
drush state:set system.maintenance_mode 1 --input-format=integer
# 2. Update code via Composer
composer update drupal/core-recommended --with-all-dependencies
# 3. Run database updates
drush updatedb
# 4. Rebuild cache
drush cache:rebuild
# 5. Take site out of maintenance mode
drush state:set system.maintenance_mode 0 --input-format=integer
Automated Security Updates
Consider using the Project Browser module or a CI/CD pipeline to automate update checks. For critical security releases, apply patches within 48 hours.
Securing settings.php
The sites/default/settings.php file contains sensitive configuration. Here is how to secure it:
<?php
// settings.php security configuration
// Trusted host patterns — prevents HTTP Host header attacks
$settings['trusted_host_patterns'] = [
'^www\.example\.com$',
'^example\.com$',
'^staging\.example\.com$',
'^localhost$',
];
// Base URL — prevents URL generation issues
$settings['base_url'] = 'https://www.example.com';
// Hash salt — used for password hashing and token generation
// Always generate a unique, random string
$settings['hash_salt'] = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890';
// Database credentials
$databases['default']['default'] = [
'database' => 'drupal_db',
'username' => 'drupal_user',
'password' => 'strong_password_here',
'host' => 'localhost',
'port' => '3306',
'driver' => 'mysql',
'prefix' => '',
'charset' => 'utf8mb4',
];
// Private file path — outside web root if possible
$settings['file_private_path'] = '/var/www/private';
// Config sync directory — outside web root
$settings['config_sync_directory'] = '../config/sync';
File Permissions
Restrictive file permissions prevent attackers from modifying files if they gain access.
# Set directory permissions to 755 (rwx r-x r-x)
find web/ -type d -exec chmod 755 {} \;
# Set file permissions to 644 (rw- r-- r--)
find web/ -type f -exec chmod 644 {} \;
# Stricter permissions for sites/default
chmod 750 sites/default
chmod 640 sites/default/settings.php
chmod 640 sites/default/services.yml
# Private files directory
chmod 750 /var/www/private
# The files directory needs write access for uploads
chmod 775 sites/default/files
.htaccess Hardening
Drupal's .htaccess file protects the web root. Here are additional hardening rules:
# .htaccess hardening rules
# Block access to sensitive files
<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml|md)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
</IfModule>
</FilesMatch>
# Block access to configuration files
<FilesMatch "(\.(yaml|yml)|settings\.php|services\.yml)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
</IfModule>
</FilesMatch>
# Prevent PHP execution in files directory
<Directory "sites/default/files">
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
</Directory>
services.yml Security
The services.yml file configures Drupal services:
# services.yml security settings
parameters:
# Trusted host patterns — required for production
trusted_host_patterns:
- '^www\.example\.com$'
- '^example\.com$'
# CORS configuration
cors.config:
enabled: true
allowedHeaders:
- 'x-csrf-token'
- 'authorization'
- 'content-type'
- 'accept'
- 'origin'
- 'x-requested-with'
allowedMethods:
- 'GET'
- 'POST'
- 'PATCH'
- 'DELETE'
allowedOrigins:
- 'https://www.example.com'
exposedHeaders: false
maxAge: 1000
supportsCredentials: true
# Disable file with .php extension uploads
# Prevents attackers from uploading and executing PHP files
file_extensions: 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'
User Login Security
Limiting Login Attempts
The core Flood control mechanism limits login attempts:
<?php
// settings.php: configure flood control
$settings['flood_limit'] = 5; // Max failed attempts
$settings['flood_window'] = 3600; // Within 1 hour (in seconds)
// This prevents brute force attacks on user passwords.
// After 5 failed attempts from the same IP within an hour,
// Drupal blocks further login attempts.
Password Policy
Drupal does not enforce password strength by default. Use the Password Policy module:
# Password policy configuration
password_policy:
minimum_length: 12
require_uppercase: true
require_lowercase: true
require_number: true
require_special_character: true
password_expiration: 90 # days
password_history: 5 # cannot reuse last 5 passwords
Two-Factor Authentication
The TFA module adds two-factor authentication using time-based one-time passwords (TOTP):
# Install the TFA module and its dependencies
composer require drupal/tfa
# Enable the module
drush pm:enable tfa
# Configure at /admin/config/people/tfa
<?php
// Programmatically check if user has TFA enabled
$tfa_enabled = \Drupal::service('tfa.api')
->isPluginActiveForUser('tfa_totp', $account);
Users scan a QR code with Google Authenticator, Authy, or any TOTP app. After scanning, they enter a 6-digit code that changes every 30 seconds.
Security Modules
Security Kit (seckit)
Security Kit provides comprehensive security headers:
# Security Kit configuration
seckit:
# Content Security Policy headers
csp:
default-src: 'self'
script-src: 'self'
style-src: 'self' 'unsafe-inline'
img-src: 'self' data:
# X-Frame-Options
x_frame: SAMEORIGIN
# X-Content-Type-Options
x_content_type: nosniff
# Feature Policy
feature_policy: "geolocation 'none'; microphone 'none'; camera 'none'"
Paranoia Module
The Paranoia module locks down Drupal's configuration system, preventing administrators from making dangerous changes through the UI.
Username Enumeration Prevention
By default, Drupal reveals whether a username exists during login. This module prevents attackers from discovering valid usernames.
Regular Security Audits
Use the Security Review module to audit your site:
# Install security review module
composer require drupal/security_review
drush pm:enable security_review
# Run security checks via Drush
drush security_review:run
# View results at /admin/reports/security-review
The module checks:
- File system permissions
- Error reporting settings
- Input validation
- Database configuration
- Trusted host patterns
- Private file paths
- PHP execution in files directory
Securing Cron
Cron can be triggered via HTTP, which allows anyone to trigger it if they know the URL. Secure it with a cron key:
<?php
// settings.php: set a cron key
$settings['cron_key'] = 'your-secure-random-cron-key-here';
# Trigger cron securely using the key
# Only someone with the key can trigger cron
curl -s https://www.example.com/cron/YOUR-KEY-HERE
# Or use Drush instead (recommended)
drush cron
SSL/HTTPS Configuration
Always serve Drupal over HTTPS:
<?php
// settings.php: force HTTPS
$settings['reverse_proxy'] = true;
$settings['reverse_proxy_addresses'] = [$_SERVER['REMOTE_ADDR']];
// Or redirect HTTP to HTTPS in .htaccess
// RewriteEngine On
// RewriteCond %{HTTPS} off
// RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# .htaccess: prevent mixed content by redirecting to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# Enable HSTS (HTTP Strict Transport Security)
# Tells browsers to always use HTTPS for this domain
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Common Mistakes
Not subscribing to security advisories: The Drupal security team announces vulnerabilities on Drupal.org. If you do not monitor these announcements, you might not know about critical patches until it is too late.
Running outdated contributed modules: Even if Drupal core is updated, contributed modules can have vulnerabilities. Run
composer outdatedregularly and update all modules.Using default file permissions: Many hosting environments set permissive file permissions. Restrict them to 755 for directories and 644 for files, and make sure the files directory does not allow PHP execution.
Not configuring trusted host patterns: Without trusted host patterns, an attacker can use HTTP Host header injection to poison cache, reset passwords, or generate malicious links.
Disabling update notifications: Some administrators disable the Update Status module because they find the notifications annoying. This hides critical security update information.
Practice Questions
- What is the difference between SA-CORE and SA-CONTRIB advisories, and how should you respond to each?
- You find that Drupal is leaking valid usernames through the login form. How do you fix this?
- What would happen if you set the files directory to 777 permissions, and why is this dangerous?
- Challenge: Create a security hardening checklist for a Drupal production site. Include at least 15 items covering file permissions, configuration, modules, network security, and monitoring. For each item, write the command or configuration step needed.
FAQ
Mini Project
Goal: Harden a Drupal site for production deployment.
- Scan your current Drupal installation for security issues using the Security Review module
- Fix all issues found in the scan (file permissions, trusted hosts, error reporting, etc.)
- Install and configure Security Kit with CSP headers, X-Frame-Options, and HSTS
- Set up TFA (two-factor authentication) for all administrator accounts
- Configure password policy: minimum 12 characters, uppercase, lowercase, number, special character, expire every 90 days
- Set up a cron key and automate cron via a system cron job instead of HTTP
- Write a bash script that performs a daily security check: verifies file permissions, checks for Drupal security updates, and emails the report to the admin
What's Next
Now that you understand security hardening, proceed to custom module development to extend Drupal with your own functionality. Then explore Drupal API to learn about render arrays, entity queries, and the service container.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro