WordPress Security Logging: Best Practices and Implementation
Implement comprehensive security logging in WordPress to detect threats, investigate incidents, and meet compliance requirements.
Introduction
Security logging is essential for detecting attacks, investigating breaches, and meeting compliance requirements. Proper logging captures security-relevant events without overwhelming your storage or impacting performance.
What to Log for Security
Focus logging on security-critical events:
- Authentication - Successful and failed logins, password resets
- Authorization - Permission changes, role modifications
- Data access - Sensitive data views and exports
- Configuration changes - Settings modifications, plugin changes
- Error conditions - Security-related errors and exceptions
- Administrative actions - User management, content deletion
Building a Security Logger
Create a comprehensive logging system:
Logger Class
class WPFS_Security_Logger {
private static $instance = null;
private $table_name;
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
global $wpdb;
$this->table_name = $wpdb->prefix . 'security_logs';
}
public function log($event_type, $message, $data = array()) {
global $wpdb;
$log_entry = array(
'event_type' => sanitize_key($event_type),
'message' => sanitize_text_field($message),
'user_id' => get_current_user_id(),
'user_ip' => $this->get_client_ip(),
'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
'request_uri' => substr($_SERVER['REQUEST_URI'] ?? '', 0, 255),
'request_method' => $_SERVER['REQUEST_METHOD'] ?? '',
'event_data' => wp_json_encode($data),
'created_at' => current_time('mysql'),
);
$wpdb->insert($this->table_name, $log_entry);
// Alert on critical events
if ($this->is_critical_event($event_type)) {
$this->send_alert($log_entry);
}
}
private function get_client_ip() {
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : 'invalid';
}
private function is_critical_event($type) {
$critical = array(
'brute_force_detected',
'admin_login_failed',
'malware_detected',
'file_modified',
'settings_changed',
);
return in_array($type, $critical);
}
}
Logging Authentication Events
// Log successful logins
add_action('wp_login', function($user_login, $user) {
$logger = WPFS_Security_Logger::get_instance();
$logger->log('login_success', "User logged in: {$user_login}", array(
'user_id' => $user->ID,
'roles' => $user->roles,
));
}, 10, 2);
// Log failed logins
add_action('wp_login_failed', function($username) {
$logger = WPFS_Security_Logger::get_instance();
$logger->log('login_failed', "Failed login attempt: {$username}", array(
'username_attempted' => $username,
'username_exists' => username_exists($username) ? true : false,
));
});
// Log password resets
add_action('password_reset', function($user, $new_pass) {
$logger = WPFS_Security_Logger::get_instance();
$logger->log('password_reset', "Password reset for: {$user->user_login}", array(
'user_id' => $user->ID,
));
}, 10, 2);
Logging Administrative Actions
// Log option changes
add_action('updated_option', function($option, $old_value, $value) {
$sensitive_options = array(
'siteurl', 'home', 'admin_email', 'users_can_register',
'default_role', 'blog_public', 'permalink_structure',
);
if (in_array($option, $sensitive_options)) {
$logger = WPFS_Security_Logger::get_instance();
$logger->log('option_changed', "Option modified: {$option}", array(
'option_name' => $option,
'old_value' => is_string($old_value) ? substr($old_value, 0, 100) : 'complex',
'new_value' => is_string($value) ? substr($value, 0, 100) : 'complex',
));
}
}, 10, 3);
// Log user role changes
add_action('set_user_role', function($user_id, $role, $old_roles) {
$logger = WPFS_Security_Logger::get_instance();
$logger->log('role_changed', "User role modified", array(
'affected_user' => $user_id,
'new_role' => $role,
'old_roles' => $old_roles,
));
}, 10, 3);
Log Rotation and Retention
Manage log storage efficiently:
// Daily log cleanup
add_action('wpfs_daily_cleanup', function() {
global $wpdb;
$table = $wpdb->prefix . 'security_logs';
// Keep 90 days of logs
$retention_days = 90;
$cutoff = date('Y-m-d', strtotime("-{$retention_days} days"));
$wpdb->query($wpdb->prepare(
"DELETE FROM {$table} WHERE created_at < %s",
$cutoff
));
// Archive critical events longer
// Export to external storage if needed
});
// Schedule cleanup
if (!wp_next_scheduled('wpfs_daily_cleanup')) {
wp_schedule_event(time(), 'daily', 'wpfs_daily_cleanup');
}
Log Analysis and Alerting
Extract security insights from logs:
// Detect brute force patterns
function analyze_login_patterns() {
global $wpdb;
$table = $wpdb->prefix . 'security_logs';
// Failed logins per IP in last hour
$results = $wpdb->get_results("
SELECT user_ip, COUNT(*) as attempts
FROM {$table}
WHERE event_type = 'login_failed'
AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)
GROUP BY user_ip
HAVING attempts > 5
");
foreach ($results as $row) {
// Block IP or send alert
$logger = WPFS_Security_Logger::get_instance();
$logger->log('brute_force_detected', "Potential brute force from {$row->user_ip}", array(
'attempts' => $row->attempts,
'ip' => $row->user_ip,
));
}
}
Compliance Considerations
- GDPR: Anonymize personal data in logs after retention period
- PCI DSS: Log all access to cardholder data
- HIPAA: Maintain audit trails for 6 years minimum
- SOC 2: Demonstrate security monitoring practices
Conclusion
Comprehensive security logging provides visibility into your WordPress security posture. Log authentication, authorization, and administrative events while managing storage through rotation and retention policies.
Written by Sarah Chen
WP Folder Shield Team