diff --git a/guides/OpenLiteSpeed_htaccess_Module_Documentation.md b/guides/OpenLiteSpeed_htaccess_Module_Documentation.md new file mode 100644 index 000000000..b0c1dd2a8 --- /dev/null +++ b/guides/OpenLiteSpeed_htaccess_Module_Documentation.md @@ -0,0 +1,2791 @@ +# CyberPanel OpenLiteSpeed Module - Complete Usage Guide + +**Version:** 2.2.0 +**Last Updated:** December 28, 2025 +**Status:** Production Ready + +--- + +## Table of Contents + +1. [Getting Started](#getting-started) +2. [Header Directives](#1-header-directives) +3. [Request Header Directives](#2-request-header-directives) +4. [Environment Variables](#3-environment-variables) +5. [Access Control](#4-access-control) +6. [Redirect Directives](#5-redirect-directives) +7. [Error Documents](#6-error-documents) +8. [FilesMatch Directives](#7-filesmatch-directives) +9. [Expires Directives](#8-expires-directives) +10. [PHP Directives](#9-php-directives) +11. [Brute Force Protection](#10-brute-force-protection) +12. [CyberPanel Integration](#cyberpanel-integration) +13. [Real-World Examples](#real-world-examples) +14. [Troubleshooting](#troubleshooting) + +--- + +## Getting Started + +### What is This Module? + +The CyberPanel OpenLiteSpeed Module brings Apache .htaccess compatibility to OpenLiteSpeed servers. It allows you to use familiar Apache directives without switching web servers. + +### Quick Start + +1. **Module is pre-installed** on CyberPanel servers +2. **Create .htaccess** in your website's public_html directory +3. **Add directives** from this guide +4. **Test** using curl or browser + +### Basic .htaccess Example + +```apache +# Security headers +Header set X-Frame-Options "SAMEORIGIN" +Header set X-Content-Type-Options "nosniff" + +# Enable brute force protection +BruteForceProtection On +``` + +--- + +## 1. Header Directives + +### What Are HTTP Headers? + +HTTP headers are metadata sent with web responses. They control browser behavior, caching, security, and more. + +### Supported Operations + +| Operation | Purpose | Syntax | +|-----------|---------|--------| +| **set** | Set header (replaces existing) | `Header set Name "Value"` | +| **unset** | Remove header | `Header unset Name` | +| **append** | Append to existing header | `Header append Name "Value"` | +| **merge** | Add if not present | `Header merge Name "Value"` | +| **add** | Always add (allows duplicates) | `Header add Name "Value"` | + +### How to Use + +#### Basic Security Headers + +**What it does:** Protects against clickjacking, XSS, and MIME sniffing. + +```apache +# Prevent site from being embedded in iframe (clickjacking protection) +Header set X-Frame-Options "SAMEORIGIN" + +# Prevent MIME type sniffing +Header set X-Content-Type-Options "nosniff" + +# Enable XSS filter in browsers +Header set X-XSS-Protection "1; mode=block" + +# Control referrer information +Header set Referrer-Policy "strict-origin-when-cross-origin" + +# Restrict browser features +Header set Permissions-Policy "geolocation=(), microphone=(), camera=()" +``` + +**Testing:** +```bash +curl -I https://yourdomain.com | grep -E "X-Frame|X-Content|X-XSS" +``` + +#### Cache Control Headers + +**What it does:** Controls how browsers cache your content. + +```apache +# Cache for 1 year (static assets) +Header set Cache-Control "max-age=31536000, public, immutable" + +# No caching (dynamic content) +Header set Cache-Control "no-cache, no-store, must-revalidate" +Header set Pragma "no-cache" +Header set Expires "0" + +# Cache for 1 hour +Header set Cache-Control "max-age=3600, public" +``` + +**Testing:** +```bash +curl -I https://yourdomain.com/style.css | grep Cache-Control +``` + +#### CORS Headers + +**What it does:** Allows cross-origin requests (needed for APIs, fonts, n8n, etc.). + +```apache +# Allow all origins +Header set Access-Control-Allow-Origin "*" + +# Allow specific origin +Header set Access-Control-Allow-Origin "https://app.example.com" + +# Allow specific methods +Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + +# Allow specific headers +Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With" + +# Allow credentials +Header set Access-Control-Allow-Credentials "true" + +# Preflight cache duration +Header set Access-Control-Max-Age "86400" +``` + +**Testing:** +```bash +curl -I -H "Origin: https://example.com" https://yourdomain.com/api +``` + +#### Remove Server Identification + +**What it does:** Hides server information from attackers. + +```apache +Header unset Server +Header unset X-Powered-By +Header unset X-LiteSpeed-Tag +``` + +**Testing:** +```bash +curl -I https://yourdomain.com | grep -E "Server|X-Powered" +# Should return nothing +``` + +### CyberPanel Integration + +#### Via File Manager + +1. Log into **CyberPanel** +2. Go to **File Manager** +3. Navigate to `/home/yourdomain.com/public_html` +4. Create or edit `.htaccess` +5. Add header directives +6. Save and test + +#### Via SSH + +```bash +# Navigate to website directory +cd /home/yourdomain.com/public_html + +# Edit .htaccess +nano .htaccess + +# Add your headers +Header set X-Frame-Options "SAMEORIGIN" + +# Save (Ctrl+X, Y, Enter) + +# Test +curl -I https://yourdomain.com | grep X-Frame +``` + +### Common Use Cases + +#### WordPress Security Headers + +```apache +# WordPress-specific security +Header set X-Frame-Options "SAMEORIGIN" +Header set X-Content-Type-Options "nosniff" +Header set X-XSS-Protection "1; mode=block" +Header set Referrer-Policy "strict-origin-when-cross-origin" +Header unset X-Powered-By + +# Disable XML-RPC header +Header unset X-Pingback +``` + +#### n8n CORS Configuration + +```apache +# Allow n8n webhooks +Header set Access-Control-Allow-Origin "https://your-n8n-instance.com" +Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" +Header set Access-Control-Allow-Headers "Content-Type, Authorization" +Header set Access-Control-Allow-Credentials "true" +``` + +#### API Response Headers + +```apache +# JSON API headers +Header set Content-Type "application/json; charset=utf-8" +Header set X-Content-Type-Options "nosniff" +Header set Access-Control-Allow-Origin "*" +Header set Cache-Control "no-cache, no-store, must-revalidate" +``` + +--- + +## 2. Request Header Directives + +### What Are Request Headers? + +Request headers are sent FROM the client TO the server. This feature lets you modify or add headers before they reach your PHP application. + +### How It Works + +Since OpenLiteSpeed's LSIAPI doesn't support direct request header modification, these are implemented as **environment variables** accessible in PHP via `$_SERVER`. + +### Supported Operations + +| Operation | Syntax | Result | +|-----------|--------|--------| +| **set** | `RequestHeader set Name "Value"` | `$_SERVER['HTTP_NAME']` | +| **unset** | `RequestHeader unset Name` | Header removed | + +### How to Use + +#### SSL/HTTPS Detection (Behind Proxy) + +**What it does:** Tells your application the request came via HTTPS (when behind Cloudflare, nginx proxy, etc.). + +```apache +# Set HTTPS protocol headers +RequestHeader set X-Forwarded-Proto "https" +RequestHeader set X-Forwarded-SSL "on" +RequestHeader set X-Real-IP "%{REMOTE_ADDR}e" +``` + +**PHP Usage:** +```php + +``` + +#### Application Environment Identification + +**What it does:** Tags requests with environment information. + +```apache +# Identify environment +RequestHeader set X-Environment "production" +RequestHeader set X-Server-Location "us-east-1" +RequestHeader set X-Request-Start "%{REQUEST_TIME}e" +``` + +**PHP Usage:** +```php + +``` + +#### Custom Backend Headers + +**What it does:** Passes custom information to your application. + +```apache +# Custom application headers +RequestHeader set X-API-Version "v2" +RequestHeader set X-Feature-Flags "new-ui,beta-features" +RequestHeader set X-Client-Type "web" +``` + +**PHP Usage:** +```php + +``` + +### CyberPanel Integration + +#### For WordPress Behind Cloudflare + +```apache +# In /home/yourdomain.com/public_html/.htaccess +RequestHeader set X-Forwarded-Proto "https" +RequestHeader set X-Forwarded-SSL "on" + +# WordPress will now correctly detect HTTPS +``` + +**Verify in WordPress:** +```php +// Add to wp-config.php if needed +if ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { + $_SERVER['HTTPS'] = 'on'; +} +``` + +### Common Use Cases + +#### Cloudflare + WordPress + +```apache +RequestHeader set X-Forwarded-Proto "https" +RequestHeader set X-Forwarded-SSL "on" +RequestHeader set X-Real-IP "%{REMOTE_ADDR}e" +``` + +#### Laravel Behind Load Balancer + +```apache +RequestHeader set X-Forwarded-Proto "https" +RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}e" +``` + +--- + +## 3. Environment Variables + +### What Are Environment Variables? + +Environment variables are key-value pairs accessible in your PHP application. They're useful for configuration, feature flags, and conditional logic. + +### Supported Directives + +| Directive | Purpose | Syntax | +|-----------|---------|--------| +| **SetEnv** | Set static variable | `SetEnv NAME value` | +| **SetEnvIf** | Conditional set (case-sensitive) | `SetEnvIf attribute regex VAR=value` | +| **SetEnvIfNoCase** | Conditional set (case-insensitive) | `SetEnvIfNoCase attribute regex VAR=value` | +| **BrowserMatch** | Detect browser | `BrowserMatch regex VAR=value` | + +### How to Use + +#### Static Configuration Variables + +**What it does:** Sets application configuration accessible in PHP. + +```apache +# Application settings +SetEnv APPLICATION_ENV production +SetEnv DB_HOST localhost +SetEnv DB_NAME myapp_db +SetEnv API_ENDPOINT https://api.example.com +SetEnv FEATURE_FLAG_NEW_UI enabled +SetEnv DEBUG_MODE off +``` + +**PHP Usage:** +```php + +``` + +#### Conditional Variables (SetEnvIf) + +**What it does:** Sets variables based on request properties. + +##### Supported Conditions + +- `Request_URI` - URL path +- `Request_Method` - HTTP method (GET, POST, etc.) +- `User-Agent` - Browser/client identifier +- `Host` - Domain name +- `Referer` - Referrer URL +- `Query_String` - URL parameters +- `Remote_Addr` - Client IP address + +**Examples:** + +```apache +# Detect API requests +SetEnvIf Request_URI "^/api/" IS_API_REQUEST=1 + +# Detect POST requests +SetEnvIf Request_Method "POST" IS_POST_REQUEST=1 + +# Detect specific domain +SetEnvIf Host "^beta\." IS_BETA_SITE=1 + +# Detect search queries +SetEnvIf Query_String "search=" HAS_SEARCH=1 + +# Detect local development +SetEnvIf Remote_Addr "^127\.0\.0\.1$" IS_LOCAL=1 +``` + +**PHP Usage:** +```php + +``` + +#### Browser Detection + +**What it does:** Identifies the user's browser for compatibility handling. + +```apache +# Case-insensitive browser detection +SetEnvIfNoCase User-Agent "mobile|android|iphone|ipad" IS_MOBILE=1 +SetEnvIfNoCase User-Agent "bot|crawler|spider|scraper" IS_BOT=1 +SetEnvIfNoCase User-Agent "MSIE|Trident" IS_IE=1 + +# Specific browser matching +BrowserMatch "Chrome" IS_CHROME=1 +BrowserMatch "Firefox" IS_FIREFOX=1 +BrowserMatch "Safari" IS_SAFARI=1 +BrowserMatch "Edge" IS_EDGE=1 +``` + +**PHP Usage:** +```php +Please use a modern browser'; +} +?> +``` + +### CyberPanel Integration + +#### Environment-Specific Configuration + +```apache +# In /home/yourdomain.com/public_html/.htaccess + +# Production settings +SetEnv APPLICATION_ENV production +SetEnv DEBUG_MODE off +SetEnv CACHE_ENABLED on + +# Database connection +SetEnv DB_HOST localhost +SetEnv DB_NAME wp_database + +# Feature flags +SetEnv ENABLE_CDN on +SetEnv ENABLE_CACHE on +``` + +**WordPress Usage (wp-config.php):** +```php + +``` + +### Common Use Cases + +#### Mobile Detection + Redirect + +```apache +# Detect mobile users +SetEnvIfNoCase User-Agent "mobile|android|iphone" IS_MOBILE=1 + +# Redirect mobile to subdomain (using PHP) +``` + +**PHP redirect:** +```php + +``` + +#### API Rate Limiting Preparation + +```apache +# Tag API requests +SetEnvIf Request_URI "^/api/" IS_API=1 +SetEnvIf Request_Method "POST" IS_POST=1 +``` + +**PHP rate limiting:** +```php + +``` + +--- + +## 4. Access Control + +### What is Access Control? + +Access control restricts who can access your website based on IP addresses. Perfect for staging sites, admin panels, or development environments. + +### Directives + +| Directive | Syntax | Description | +|-----------|--------|-------------| +| **Order** | `Order deny,allow` or `Order allow,deny` | Set evaluation order | +| **Allow** | `Allow from IP/CIDR` | Allow specific IP | +| **Deny** | `Deny from IP/CIDR` | Deny specific IP | + +### Supported IP Formats + +- **Single IP:** `192.168.1.100` +- **CIDR Range:** `192.168.1.0/24` (entire subnet) +- **Large Ranges:** `10.0.0.0/8` (entire class) +- **IPv6:** `2001:db8::/32` +- **Wildcard:** `all` (everyone) + +### How Order Works + +#### Order deny,allow + +1. Check **Deny** list first +2. Then check **Allow** list +3. **Allow overrides Deny** +4. Default: **DENY** if not in either list + +```apache +Order deny,allow +Deny from all +Allow from 192.168.1.100 +# Result: Only 192.168.1.100 can access +``` + +#### Order allow,deny + +1. Check **Allow** list first +2. Then check **Deny** list +3. **Deny overrides Allow** +4. Default: **ALLOW** if not in either list + +```apache +Order allow,deny +Allow from all +Deny from 192.168.1.100 +# Result: Everyone except 192.168.1.100 can access +``` + +### How to Use + +#### Block All Except Specific IPs (Recommended for Staging) + +```apache +# Only allow office IP and VPN +Order deny,allow +Deny from all +Allow from 203.0.113.50 # Office IP +Allow from 192.168.1.0/24 # Office LAN +Allow from 10.8.0.0/24 # VPN range +``` + +**Use case:** Development/staging sites, admin areas + +**Testing:** +```bash +# From allowed IP +curl https://staging.example.com +# Should work + +# From other IP +curl https://staging.example.com +# Should get 403 Forbidden +``` + +#### Allow All Except Specific IPs + +```apache +# Block known attackers +Order allow,deny +Allow from all +Deny from 198.51.100.50 # Banned IP +Deny from 203.0.113.0/24 # Banned subnet +``` + +**Use case:** Blocking spam IPs, attack sources + +#### Protect Admin Directory + +```apache +# In /home/yourdomain.com/public_html/admin/.htaccess +Order deny,allow +Deny from all +Allow from 192.168.1.0/24 # Office network +Allow from 203.0.113.100 # Your home IP +``` + +**Use case:** WordPress wp-admin protection + +### CyberPanel Integration + +#### Protect Staging Site + +1. Create subdomain `staging.yourdomain.com` in CyberPanel +2. Navigate to `/home/staging.yourdomain.com/public_html` +3. Create `.htaccess`: + +```apache +# Staging site - Office only +Order deny,allow +Deny from all +Allow from YOUR.OFFICE.IP.HERE +Allow from YOUR.HOME.IP.HERE +``` + +4. Test: +```bash +# Get your IP +curl ifconfig.me + +# Test access +curl -I https://staging.yourdomain.com +# Should see 403 if not allowed +``` + +#### Protect WordPress Admin + +```apache +# In /home/yourdomain.com/public_html/wp-admin/.htaccess +Order deny,allow +Deny from all +Allow from 203.0.113.50 # Your IP +``` + +**Important:** This creates TWO layers of protection: +1. IP restriction (from .htaccess) +2. Login authentication (from WordPress) + +#### Protect CyberPanel Access + +```apache +# In /usr/local/CyberCP/public/.htaccess (if web accessible) +Order deny,allow +Deny from all +Allow from 127.0.0.1 # localhost +Allow from 192.168.1.0/24 # Your network +``` + +### Common Use Cases + +#### Development Environment + +```apache +# Dev site - developers only +Order deny,allow +Deny from all +Allow from 192.168.1.0/24 # Office LAN +Allow from 10.8.0.0/24 # VPN +Allow from 203.0.113.50 # Lead developer home +``` + +#### Geographic Restriction + +```apache +# Block specific countries (you need to maintain IP list) +Order allow,deny +Allow from all +Deny from 198.51.100.0/24 # Country X subnet +Deny from 203.0.113.0/24 # Country Y subnet +``` + +#### API Endpoint Protection + +```apache +# In /home/yourdomain.com/public_html/api/.htaccess +Order deny,allow +Deny from all +Allow from 10.0.0.0/8 # Internal network +Allow from 172.16.0.0/12 # Private network +``` + +### Troubleshooting + +**Problem:** Getting 403 even from allowed IP + +**Solution:** +1. Check your actual IP: `curl ifconfig.me` +2. Verify CIDR: `192.168.1.0/24` covers `192.168.1.1` to `192.168.1.254` +3. Check logs: `tail -f /usr/local/lsws/logs/error.log` + +**Problem:** Access control not working + +**Solution:** +1. Verify module loaded: `ls -la /usr/local/lsws/modules/cyberpanel_ols.so` +2. Check .htaccess permissions: `chmod 644 .htaccess` +3. Restart OpenLiteSpeed: `/usr/local/lsws/bin/lswsctrl restart` + +--- + +## 5. Redirect Directives + +### What Are Redirects? + +Redirects tell browsers to go to a different URL. Essential for SEO, site migrations, and URL structure changes. + +### Directives + +| Directive | Syntax | Use Case | +|-----------|--------|----------| +| **Redirect** | `Redirect [code] /old /new` | Simple path redirects | +| **RedirectMatch** | `RedirectMatch [code] regex target` | Pattern-based redirects | + +### Status Codes + +| Code | Name | When to Use | +|------|------|-------------| +| **301** | Permanent | SEO-friendly, URL has moved forever | +| **302** | Temporary | URL temporarily moved, may change back | +| **303** | See Other | Redirect after POST (form submission) | +| **410** | Gone | Resource permanently deleted | + +### How to Use + +#### Simple Redirects + +**What it does:** Redirects one path to another. + +```apache +# Old page to new page +Redirect 301 /old-page.html /new-page.html + +# Old directory to new directory +Redirect 301 /old-blog /blog + +# Use keywords instead of codes +Redirect permanent /old-url /new-url +Redirect temp /maintenance /coming-soon +``` + +**Testing:** +```bash +curl -I https://yourdomain.com/old-page.html +# Should show: HTTP/1.1 301 Moved Permanently +# Location: https://yourdomain.com/new-page.html +``` + +#### Force HTTPS + +**What it does:** Redirects HTTP to HTTPS. + +```apache +# Redirect HTTP to HTTPS +Redirect 301 / https://yourdomain.com/ +``` + +**Better Alternative (checks if already HTTPS):** +```apache +SetEnvIf Request_URI ".*" IS_HTTP=1 +# Use with PHP to avoid redirect loop +``` + +**PHP solution:** +```php + +``` + +#### Force WWW or Non-WWW + +**What it does:** Standardizes domain format for SEO. + +```apache +# Force www +Redirect 301 / https://www.yourdomain.com/ + +# Force non-www (use RedirectMatch) +RedirectMatch 301 ^(.*)$ https://yourdomain.com$1 +``` + +#### Pattern-Based Redirects (RedirectMatch) + +**What it does:** Uses regex to match and redirect URLs. + +```apache +# Blog restructuring +RedirectMatch 301 ^/blog/(.*)$ /news/$1 +# /blog/post-1 → /news/post-1 + +# Product ID migration +RedirectMatch 301 ^/product-([0-9]+)$ /item/$1 +# /product-123 → /item/123 + +# Year/month/title to title +RedirectMatch 301 ^/blog/([0-9]{4})/([0-9]{2})/(.*)$ /articles/$3 +# /blog/2024/12/my-post → /articles/my-post + +# Category reorganization +RedirectMatch 301 ^/category/(.*)$ /topics/$1 +``` + +**Testing:** +```bash +curl -I https://yourdomain.com/blog/my-post +# Should redirect to /news/my-post +``` + +### CyberPanel Integration + +#### Site Migration (Old Domain to New) + +```apache +# In old site's .htaccess +Redirect 301 / https://new-domain.com/ +``` + +**Steps:** +1. Keep old domain active in CyberPanel +2. Add redirect to `/home/old-domain.com/public_html/.htaccess` +3. Monitor traffic migration +4. After 6 months, can delete old domain + +#### WordPress Permalink Change + +**Scenario:** Changed permalinks from `/?p=123` to `/blog/post-title` + +```apache +# WordPress handles this automatically, but for custom: +RedirectMatch 301 ^/\?p=([0-9]+)$ /blog/post-$1 +``` + +#### E-commerce URL Update + +```apache +# Old: /products/view/123 +# New: /shop/product-123 + +RedirectMatch 301 ^/products/view/([0-9]+)$ /shop/product-$1 +``` + +### Common Use Cases + +#### Complete Site Redesign + +```apache +# Redirect old structure to new +RedirectMatch 301 ^/about-us$ /about +RedirectMatch 301 ^/contact-us$ /contact +RedirectMatch 301 ^/services/(.*)$ /solutions/$1 +RedirectMatch 301 ^/blog/(.*)$ /news/$1 +``` + +#### Affiliate Link Management + +```apache +# Short URLs for affiliate links +Redirect 302 /go/amazon https://amazon.com/your-affiliate-link +Redirect 302 /go/product https://example.com/long-url-here +``` + +#### Seasonal Campaigns + +```apache +# Temporary campaign redirect +Redirect 302 /sale /christmas-sale-2025 +Redirect 302 /promo /black-friday +``` + +#### Remove .html Extensions (SEO) + +```apache +# Old: /page.html +# New: /page + +RedirectMatch 301 ^/(.*)/index\.html$ /$1/ +RedirectMatch 301 ^/(.*)[^/]\.html$ /$1 +``` + +### Troubleshooting + +**Problem:** Redirect loop + +**Solution:** Check for conflicting rules: +```apache +# BAD - Creates loop +Redirect 301 / https://example.com/ +Redirect 301 / https://www.example.com/ + +# GOOD - Use one or the other +Redirect 301 / https://www.example.com/ +``` + +**Problem:** Redirect not working + +**Solution:** +1. Clear browser cache (redirects are cached!) +2. Test with curl: `curl -I https://yoursite.com/old-page` +3. Check .htaccess syntax +4. Restart OpenLiteSpeed + +--- + +## 6. Error Documents + +### What Are Error Documents? + +Custom error pages shown when errors occur (404 Not Found, 500 Internal Server Error, etc.). + +### Supported Error Codes + +| Code | Error | When It Happens | +|------|-------|-----------------| +| **400** | Bad Request | Malformed request | +| **401** | Unauthorized | Authentication required | +| **403** | Forbidden | Access denied | +| **404** | Not Found | Page doesn't exist | +| **500** | Internal Server Error | Server-side error | +| **502** | Bad Gateway | Proxy/backend error | +| **503** | Service Unavailable | Server overloaded/maintenance | + +### Syntax + +```apache +ErrorDocument +``` + +### How to Use + +#### HTML Error Pages + +**What it does:** Shows custom-designed error pages. + +```apache +# Custom error pages +ErrorDocument 404 /errors/404.html +ErrorDocument 500 /errors/500.html +ErrorDocument 403 /errors/403.html +ErrorDocument 503 /errors/maintenance.html +``` + +**Create error pages:** + +```bash +mkdir -p /home/yourdomain.com/public_html/errors +``` + +**404.html example:** +```html + + + + Page Not Found + + + +

404 - Page Not Found

+

The page you're looking for doesn't exist.

+ Go to Homepage + + +``` + +**Testing:** +```bash +curl https://yourdomain.com/nonexistent-page +# Should show your custom 404 page +``` + +#### Inline Messages + +**What it does:** Shows simple text message. + +```apache +ErrorDocument 403 "Access Denied - Contact Administrator" +ErrorDocument 404 "Page Not Found - Please check the URL" +``` + +#### WordPress-Friendly Error Pages + +**What it does:** Routes errors through WordPress. + +```apache +# Let WordPress handle 404s +ErrorDocument 404 /index.php?error=404 +``` + +**WordPress theme (404.php):** +```php + +

Page Not Found

+

Sorry, this page doesn't exist.

+ +``` + +### CyberPanel Integration + +#### Setup Custom Error Pages + +**Step 1:** Create error directory +```bash +cd /home/yourdomain.com/public_html +mkdir errors +cd errors +``` + +**Step 2:** Create error page files +```bash +nano 404.html +# Add custom HTML +# Save (Ctrl+X, Y, Enter) + +nano 500.html +# Add custom HTML +# Save +``` + +**Step 3:** Configure .htaccess +```apache +# In /home/yourdomain.com/public_html/.htaccess +ErrorDocument 404 /errors/404.html +ErrorDocument 500 /errors/500.html +ErrorDocument 403 /errors/403.html +``` + +**Step 4:** Test +```bash +curl https://yourdomain.com/test-404 +``` + +#### Maintenance Page + +```apache +# During maintenance +ErrorDocument 503 /maintenance.html +``` + +**maintenance.html:** +```html + + + + Maintenance + + + + +

We'll be right back!

+

Our site is undergoing maintenance.

+

Expected completion: 2 hours

+ + +``` + +**Trigger maintenance mode:** +```bash +# Temporarily disable PHP +mv index.php index.php.bak +# Site will show 503 +``` + +### Common Use Cases + +#### Professional 404 Page with Search + +**404.html:** +```html + + + + Page Not Found + + +

404 - Page Not Found

+

Try searching:

+
+ + +
+

Return to Homepage

+ + +``` + +#### Branded Error Pages + +```apache +ErrorDocument 400 /errors/400.html +ErrorDocument 401 /errors/401.html +ErrorDocument 403 /errors/403.html +ErrorDocument 404 /errors/404.html +ErrorDocument 500 /errors/500.html +ErrorDocument 502 /errors/502.html +ErrorDocument 503 /errors/503.html +``` + +Each page styled with your brand colors, logo, navigation. + +--- + +## 7. FilesMatch Directives + +### What is FilesMatch? + +FilesMatch applies directives only to files matching a regex pattern. Perfect for caching strategies, security headers per file type. + +### Syntax + +```apache + + # Directives here apply only to matching files + Header set Name "Value" + +``` + +### Common File Patterns + +| Pattern | Matches | +|---------|---------| +| `\.(jpg\|png\|gif)$` | Images | +| `\.(css\|js)$` | Stylesheets and JavaScript | +| `\.(woff2?\|ttf\|eot)$` | Fonts | +| `\.(pdf\|doc\|docx)$` | Documents | +| `\.(html\|php)$` | Dynamic pages | +| `\.json$` | JSON files | + +### How to Use + +#### Cache Static Assets (Performance Boost) + +**What it does:** Tells browsers to cache images/fonts for a long time. + +```apache +# Images - Cache for 1 year + + Header set Cache-Control "max-age=31536000, public, immutable" + Header unset ETag + Header unset Last-Modified + + +# Fonts - Cache for 1 year + + Header set Cache-Control "max-age=31536000, public, immutable" + Header set Access-Control-Allow-Origin "*" + + +# CSS/JS - Cache for 1 week (you update these more often) + + Header set Cache-Control "max-age=604800, public" + +``` + +**Testing:** +```bash +curl -I https://yourdomain.com/logo.png | grep Cache-Control +# Should show: Cache-Control: max-age=31536000, public, immutable +``` + +**Performance Impact:** +- First visit: Downloads all files +- Return visits: Loads from browser cache (instant!) +- Page load time: -50% to -80% + +#### Security Headers for HTML/PHP + +**What it does:** Applies security headers only to pages (not images). + +```apache + + Header set X-Frame-Options "SAMEORIGIN" + Header set X-Content-Type-Options "nosniff" + Header set X-XSS-Protection "1; mode=block" + Header set Referrer-Policy "strict-origin-when-cross-origin" + +``` + +#### Prevent Caching of Dynamic Content + +**What it does:** Ensures dynamic pages are never cached. + +```apache + + Header set Cache-Control "no-cache, no-store, must-revalidate" + Header set Pragma "no-cache" + Header set Expires "0" + +``` + +#### CORS for Fonts (Fix Font Loading) + +**What it does:** Allows fonts to load from CDN or different domain. + +```apache + + Header set Access-Control-Allow-Origin "*" + +``` + +**Use case:** Fixes "Font from origin has been blocked by CORS policy" errors. + +#### Download Headers for Files + +**What it does:** Forces download instead of displaying in browser. + +```apache + + Header set Content-Disposition "attachment" + Header set X-Content-Type-Options "nosniff" + +``` + +### CyberPanel Integration + +#### WordPress Performance Optimization + +```apache +# In /home/yourdomain.com/public_html/.htaccess + +# Cache WordPress static assets + + Header set Cache-Control "max-age=31536000, public, immutable" + + +# Cache CSS/JS (with version strings in WordPress) + + Header set Cache-Control "max-age=2592000, public" + + +# Don't cache WordPress admin + + Header set Cache-Control "no-cache, no-store, must-revalidate" + +``` + +**Result:** PageSpeed score +20-30 points + +#### WooCommerce Security + +```apache +# Protect sensitive files + + Order deny,allow + Deny from all + + +# JSON API security + + Header set X-Content-Type-Options "nosniff" + Header set Content-Type "application/json; charset=utf-8" + +``` + +### Common Use Cases + +#### Complete Caching Strategy + +```apache +# Aggressive caching for static assets (1 year) + + Header set Cache-Control "max-age=31536000, public, immutable" + Header unset ETag + + +# Moderate caching for CSS/JS (1 month) + + Header set Cache-Control "max-age=2592000, public" + + +# Short caching for HTML (1 hour) + + Header set Cache-Control "max-age=3600, public" + + +# No caching for dynamic content + + Header set Cache-Control "no-cache, must-revalidate" + +``` + +#### Media Library Protection + +```apache +# Prevent hotlinking (bandwidth theft) + + SetEnvIf Referer "^https://yourdomain\.com" local_ref + SetEnvIf Referer "^$" local_ref + Order deny,allow + Deny from all + Allow from env=local_ref + +``` + +--- + +## 8. Expires Directives + +### What is mod_expires? + +Alternative syntax for setting cache expiration. More concise than Cache-Control headers. + +### Directives + +```apache +ExpiresActive On +ExpiresByType mime-type base+seconds +``` + +### Time Bases + +- **A** = Access time (when user requests file) +- **M** = Modification time (when file was last modified) + +### Common Durations + +| Duration | Seconds | Example | +|----------|---------|---------| +| 1 minute | 60 | `A60` | +| 1 hour | 3600 | `A3600` | +| 1 day | 86400 | `A86400` | +| 1 week | 604800 | `A604800` | +| 1 month | 2592000 | `A2592000` | +| 1 year | 31557600 | `A31557600` | + +### How to Use + +#### Complete Expiration Strategy + +```apache +# Enable module +ExpiresActive On + +# Images - 1 year +ExpiresByType image/jpeg A31557600 +ExpiresByType image/png A31557600 +ExpiresByType image/gif A31557600 +ExpiresByType image/webp A31557600 +ExpiresByType image/svg+xml A31557600 +ExpiresByType image/x-icon A31557600 + +# CSS and JavaScript - 1 month +ExpiresByType text/css A2592000 +ExpiresByType application/javascript A2592000 +ExpiresByType application/x-javascript A2592000 +ExpiresByType text/javascript A2592000 + +# Fonts - 1 year +ExpiresByType font/ttf A31557600 +ExpiresByType font/woff A31557600 +ExpiresByType font/woff2 A31557600 +ExpiresByType application/font-woff A31557600 +ExpiresByType application/font-woff2 A31557600 + +# HTML - no cache +ExpiresByType text/html A0 + +# PDF - 1 month +ExpiresByType application/pdf A2592000 + +# JSON/XML - 1 hour +ExpiresByType application/json A3600 +ExpiresByType application/xml A3600 +``` + +**Testing:** +```bash +curl -I https://yourdomain.com/image.jpg | grep -E "Expires|Cache-Control" +``` + +### CyberPanel Integration + +#### WordPress Caching + +```apache +# In /home/yourdomain.com/public_html/.htaccess + +ExpiresActive On + +# WordPress uploads (images in wp-content/uploads) +ExpiresByType image/jpeg A31557600 +ExpiresByType image/png A31557600 +ExpiresByType image/gif A31557600 + +# WordPress theme assets +ExpiresByType text/css A2592000 +ExpiresByType application/javascript A2592000 + +# WordPress HTML (dynamic, don't cache) +ExpiresByType text/html A0 +``` + +### FilesMatch vs Expires + +**Use FilesMatch when:** +- Need multiple headers per file type +- Need complex regex patterns +- Want more control + +**Use Expires when:** +- Only setting cache expiration +- Want concise syntax +- Working with MIME types + +**Both together:** +```apache +ExpiresActive On + + + ExpiresByType image/jpeg A31557600 + Header set Cache-Control "public, immutable" + Header unset ETag + +``` + +--- + +## 9. PHP Directives + +### What Are PHP Directives? + +Change PHP configuration per-directory without editing php.ini. + +### Directives + +| Directive | Syntax | Purpose | +|-----------|--------|---------| +| **php_value** | `php_value name value` | Set numeric/string values | +| **php_flag** | `php_flag name on/off` | Set boolean (on/off) values | + +### Requirements + +- Must use **LSPHP** (not PHP-FPM) +- Must be **PHP_INI_ALL** or **PHP_INI_PERDIR** directive +- CyberPanel uses LSPHP by default ✅ + +### How to Use + +#### Memory and Execution Limits + +**What it does:** Allows scripts to use more memory/time. + +```apache +# Increase memory (default 128M) +php_value memory_limit 256M + +# Increase execution time (default 30s) +php_value max_execution_time 300 + +# Increase input time (default 60s) +php_value max_input_time 300 + +# Increase max input variables (default 1000) +php_value max_input_vars 5000 +``` + +**Use case:** WordPress imports, WooCommerce bulk operations, data processing. + +**Testing:** +```php + +``` + +#### Upload Limits + +**What it does:** Allows larger file uploads. + +```apache +# Allow 100MB uploads (default 2M) +php_value upload_max_filesize 100M +php_value post_max_size 100M + +# Increase max file uploads (default 20) +php_value max_file_uploads 50 +``` + +**Use case:** Media uploads, plugin/theme installation, backup uploads. + +**Testing:** +```php + +``` + +#### Error Handling + +**What it does:** Controls error display and logging. + +```apache +# Production (hide errors) +php_flag display_errors off +php_flag log_errors on +php_value error_log /home/yourdomain.com/logs/php_errors.log + +# Development (show errors) +php_flag display_errors on +php_value error_reporting 32767 +``` + +**Use case:** Debugging vs production security. + +#### Session Configuration + +**What it does:** Configures PHP sessions. + +```apache +# Session lifetime (1 hour) +php_value session.gc_maxlifetime 3600 + +# Session cookie (close browser = logout) +php_value session.cookie_lifetime 0 + +# Session security +php_flag session.cookie_httponly on +php_flag session.cookie_secure on +php_value session.cookie_samesite Strict +``` + +**Use case:** Login session duration, security. + +#### Timezone + +**What it does:** Sets server timezone. + +```apache +php_value date.timezone "America/New_York" +php_value date.timezone "Europe/London" +php_value date.timezone "Asia/Tokyo" +``` + +**Use case:** Correct timestamps in logs, posts, events. + +**Testing:** +```php + +``` + +### CyberPanel Integration + +#### WordPress Performance Tuning + +```apache +# In /home/yourdomain.com/public_html/.htaccess + +# WordPress recommended settings +php_value memory_limit 256M +php_value max_execution_time 300 +php_value max_input_time 300 +php_value max_input_vars 5000 +php_value upload_max_filesize 64M +php_value post_max_size 64M + +# Production error handling +php_flag display_errors off +php_flag log_errors on +php_value error_log /home/yourdomain.com/logs/php_errors.log + +# Session security +php_flag session.cookie_httponly on +php_flag session.cookie_secure on +``` + +#### WooCommerce Optimization + +```apache +# WooCommerce needs more resources +php_value memory_limit 512M +php_value max_execution_time 600 +php_value max_input_vars 10000 +php_value upload_max_filesize 128M +php_value post_max_size 128M +``` + +#### Development vs Production + +**Development .htaccess:** +```apache +php_flag display_errors on +php_value error_reporting 32767 +php_flag display_startup_errors on +php_value memory_limit 512M +``` + +**Production .htaccess:** +```apache +php_flag display_errors off +php_flag log_errors on +php_value error_log /home/yourdomain.com/logs/php_errors.log +php_value memory_limit 256M +``` + +### Common Use Cases + +#### Fix "Memory Exhausted" Error + +```apache +php_value memory_limit 512M +``` + +#### Fix "Maximum Execution Time Exceeded" + +```apache +php_value max_execution_time 300 +``` + +#### Fix "Upload Failed" (File Too Large) + +```apache +php_value upload_max_filesize 100M +php_value post_max_size 100M +``` + +#### Fix "Maximum Input Vars Exceeded" (WordPress Theme Options) + +```apache +php_value max_input_vars 10000 +``` + +### Supported Directives + +Most PHP ini settings can be changed: + +**✅ Supported:** +- memory_limit +- max_execution_time +- max_input_time +- max_input_vars +- upload_max_filesize +- post_max_size +- display_errors +- log_errors +- error_log +- error_reporting +- session.* (all session directives) +- date.timezone +- default_charset +- output_buffering + +**❌ Not Supported:** +- enable_dl (PHP_INI_SYSTEM only) +- safe_mode (deprecated) +- open_basedir (security setting) + +--- + +## 10. Brute Force Protection + +### What is Brute Force Protection? + +Built-in WordPress login protection. Limits POST requests to wp-login.php and xmlrpc.php to stop password guessing attacks. + +### Quick Start + +```apache +BruteForceProtection On +``` + +That's it! Default settings: 10 attempts per 5 minutes. + +### How It Works + +1. Tracks POST requests to `/wp-login.php` and `/xmlrpc.php` +2. Counts requests per IP address +3. Uses time-window quota system (e.g., 10 requests per 300 seconds) +4. When quota exhausted, applies action (block, log, or throttle) +5. Quota resets after time window expires + +### Phase 1 Directives (Basic) + +| Directive | Values | Default | Description | +|-----------|--------|---------|-------------| +| **BruteForceProtection** | On/Off | Off | Enable protection | +| **BruteForceAllowedAttempts** | 1-1000 | 10 | Max POST requests per window | +| **BruteForceWindow** | 60-86400 | 300 | Time window (seconds) | +| **BruteForceAction** | block/log/throttle | block | Action when limit exceeded | + +### Phase 2 Directives (Advanced) + +| Directive | Values | Default | Description | +|-----------|--------|---------|-------------| +| **BruteForceXForwardedFor** | On/Off | Off | Use X-Forwarded-For for real IP | +| **BruteForceWhitelist** | IP list | (empty) | Bypass protection for these IPs | +| **BruteForceProtectPath** | path | (none) | Additional paths to protect | + +### Actions Explained + +#### block (Recommended) + +**What it does:** Immediately returns 403 Forbidden. + +```apache +BruteForceAction block +``` + +**Response:** +``` +HTTP/1.1 403 Forbidden +Content-Type: text/html + + +403 Forbidden + +

Access Denied

+

Too many login attempts. Please try again later.

+ + +``` + +**Use case:** Production sites, maximum security. + +#### log (Monitoring) + +**What it does:** Allows request but logs to error.log. + +```apache +BruteForceAction log +``` + +**Use case:** Testing, monitoring before enabling blocking. + +**Check logs:** +```bash +grep BruteForce /usr/local/lsws/logs/error.log +``` + +#### throttle (New in v2.2.0) + +**What it does:** Applies progressive delays before responding. + +```apache +BruteForceAction throttle +``` + +**Throttle levels:** + +| Over-Limit Attempts | Level | Delay | HTTP Response | +|---------------------|-------|-------|---------------| +| 1-2 | Soft | 2 seconds | 429 Too Many Requests | +| 3-5 | Medium | 5 seconds | 429 Too Many Requests | +| 6+ | Hard | 15 seconds | 429 Too Many Requests | + +**Response includes:** +``` +HTTP/1.1 429 Too Many Requests +Retry-After: 15 +``` + +**Use case:** Slows down attackers while allowing legitimate users who forgot password. + +### How to Use + +#### Basic Protection (Small Site) + +```apache +# Simple protection +BruteForceProtection On +``` + +**Result:** Default 10 attempts per 5 minutes, then block. + +#### Strict Protection (High Security) + +```apache +# Only 3 attempts per 15 minutes +BruteForceProtection On +BruteForceAllowedAttempts 3 +BruteForceWindow 900 +BruteForceAction block +``` + +**Result:** Very strict, good for high-value targets. + +#### Moderate Protection with Throttle (Recommended) + +```apache +# 5 attempts per 5 minutes, then progressive throttle +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction throttle +``` + +**Result:** Legitimate users can still login (slowly), attackers waste time. + +#### Behind Cloudflare/Proxy + +**Problem:** All requests appear to come from proxy IP. + +**Solution:** Use X-Forwarded-For to get real client IP. + +```apache +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction throttle +BruteForceXForwardedFor On +``` + +**Important:** Only enable if behind trusted proxy (Cloudflare, nginx). + +#### With IP Whitelist + +**What it does:** Allows unlimited attempts from trusted IPs. + +```apache +BruteForceProtection On +BruteForceAllowedAttempts 3 +BruteForceWindow 900 +BruteForceAction block +BruteForceWhitelist 203.0.113.50, 192.168.1.0/24, 10.0.0.0/8 +``` + +**Use case:** Whitelist office IP, admin home IP, VPN range. + +#### Protect Custom Login Pages + +```apache +# Protect custom endpoints +BruteForceProtection On +BruteForceProtectPath /admin/login +BruteForceProtectPath /api/auth +BruteForceProtectPath /members/signin +``` + +**Default protected:** `/wp-login.php` and `/xmlrpc.php` + +### CyberPanel Integration + +#### WordPress Security Setup + +**Step 1:** Navigate to website .htaccess +```bash +cd /home/yourdomain.com/public_html +nano .htaccess +``` + +**Step 2:** Add protection +```apache +# At top of .htaccess +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction throttle +``` + +**Step 3:** Save and test +```bash +# Try multiple wrong passwords +# After 5 attempts, should get throttled +``` + +**Step 4:** Monitor logs +```bash +tail -f /usr/local/lsws/logs/error.log | grep BruteForce +``` + +#### WooCommerce + WordPress + +```apache +# Protect both WordPress and WooCommerce login +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction block +BruteForceProtectPath /my-account/ +BruteForceProtectPath /checkout/ +``` + +#### Multi-Site WordPress + +```apache +# Apply to all subsites +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction throttle +BruteForceXForwardedFor On +``` + +### Shared Memory Storage + +**Location:** `/dev/shm/ols/` + +```bash +ls -la /dev/shm/ols/ +# BFProt.shm - Stores IP quota data +# BFProt.lock - Synchronization lock +``` + +**Persistence:** Data survives OpenLiteSpeed restarts (stored in tmpfs). + +**Reset/Clear:** +```bash +# Clear all quota data +rm -f /dev/shm/ols/BFProt.* +/usr/local/lsws/bin/lswsctrl restart +``` + +**Use case:** Accidentally locked out, need to reset. + +### Monitoring and Logs + +#### View Brute Force Events + +```bash +grep BruteForce /usr/local/lsws/logs/error.log +``` + +**Sample log entries:** + +``` +[INFO] [BruteForce] Initialized: 10 attempts per 300s window, action: throttle +[WARN] [BruteForce] Warning: 192.168.1.50 has 2 attempts remaining for /wp-login.php +[NOTICE] [BruteForce] Blocked 192.168.1.50 - quota exhausted for /wp-login.php (10 attempts in 300s) +[NOTICE] [BruteForce] Throttling 192.168.1.50 (medium level, 5000ms delay) for /wp-login.php +``` + +#### Real-Time Monitoring + +```bash +# Watch in real-time +tail -f /usr/local/lsws/logs/error.log | grep BruteForce + +# Count blocked IPs today +grep "BruteForce.*Blocked" /usr/local/lsws/logs/error.log | grep "$(date +%Y-%m-%d)" | wc -l +``` + +#### Check Specific IP + +```bash +grep "BruteForce.*192.168.1.50" /usr/local/lsws/logs/error.log +``` + +### Testing Brute Force Protection + +#### Manual Test + +```bash +# Try multiple wrong passwords +for i in {1..15}; do + curl -X POST https://yourdomain.com/wp-login.php \ + -d "log=admin&pwd=wrong$i&wp-submit=Log+In" \ + -I | grep "HTTP" + sleep 1 +done + +# After BruteForceAllowedAttempts, should see: +# HTTP/1.1 403 Forbidden (if action=block) +# HTTP/1.1 429 Too Many Requests (if action=throttle) +``` + +#### Check Logs + +```bash +grep BruteForce /usr/local/lsws/logs/error.log | tail -20 +``` + +### Common Use Cases + +#### Production WordPress + +```apache +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction block +``` + +#### Behind Cloudflare + +```apache +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction throttle +BruteForceXForwardedFor On +``` + +#### Enterprise with Whitelist + +```apache +BruteForceProtection On +BruteForceAllowedAttempts 3 +BruteForceWindow 900 +BruteForceAction block +BruteForceXForwardedFor On +BruteForceWhitelist 10.0.0.0/8, 192.168.1.0/24, 203.0.113.100 +BruteForceProtectPath /admin/ +BruteForceProtectPath /api/login +``` + +### Troubleshooting + +**Problem:** Legitimate users getting blocked + +**Solution:** +```apache +# Increase allowed attempts +BruteForceAllowedAttempts 10 + +# Or use throttle instead of block +BruteForceAction throttle + +# Or whitelist their IP +BruteForceWhitelist 203.0.113.50 +``` + +**Problem:** Protection not working + +**Solution:** +```bash +# Check module loaded +ls -la /usr/local/lsws/modules/cyberpanel_ols.so + +# Check .htaccess syntax +cat /home/yourdomain.com/public_html/.htaccess | grep BruteForce + +# Check logs +grep BruteForce /usr/local/lsws/logs/error.log + +# Restart OpenLiteSpeed +/usr/local/lsws/bin/lswsctrl restart +``` + +**Problem:** Shared memory errors + +**Solution:** +```bash +# Create directory if missing +mkdir -p /dev/shm/ols + +# Set permissions +chmod 755 /dev/shm/ols + +# Restart +/usr/local/lsws/bin/lswsctrl restart +``` + +--- + +## CyberPanel Integration + +### Accessing Website Files + +#### Via CyberPanel File Manager + +1. Log into **CyberPanel** (https://yourserver:8090) +2. Click **File Manager** +3. Navigate to `/home/yourdomain.com/public_html` +4. Create or edit `.htaccess` +5. Add directives from this guide +6. Click **Save** + +#### Via SSH + +```bash +# Log in via SSH +ssh root@yourserver + +# Navigate to website +cd /home/yourdomain.com/public_html + +# Edit .htaccess +nano .htaccess + +# Add directives +# Save: Ctrl+X, Y, Enter +``` + +#### Via FTP (FileZilla) + +1. Connect via FTP +2. Navigate to `/home/yourdomain.com/public_html` +3. Download `.htaccess` +4. Edit locally +5. Upload back + +### Creating New Website + +1. **Create Website** in CyberPanel +2. **Navigate to directory:** + ```bash + cd /home/newsite.com/public_html + ``` +3. **Create .htaccess:** + ```bash + nano .htaccess + ``` +4. **Add base configuration:** + ```apache + # Security headers + Header set X-Frame-Options "SAMEORIGIN" + Header set X-Content-Type-Options "nosniff" + + # Brute force protection + BruteForceProtection On + + # Cache static assets + + Header set Cache-Control "max-age=31536000, public" + + ``` + +### WordPress on CyberPanel + +#### Complete WordPress .htaccess + +```apache +# Security Headers +Header set X-Frame-Options "SAMEORIGIN" +Header set X-Content-Type-Options "nosniff" +Header set X-XSS-Protection "1; mode=block" +Header unset X-Powered-By + +# Brute Force Protection +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction throttle + +# Performance - Cache Static Assets + + Header set Cache-Control "max-age=31536000, public, immutable" + + + + Header set Cache-Control "max-age=2592000, public" + + +# PHP Configuration +php_value memory_limit 256M +php_value upload_max_filesize 64M +php_value post_max_size 64M +php_value max_execution_time 300 +php_flag display_errors off + +# WordPress Rewrite Rules (leave as-is) +# BEGIN WordPress + +RewriteEngine On +RewriteBase / +RewriteRule ^index\.php$ - [L] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . /index.php [L] + +# END WordPress +``` + +### Staging Environment + +```apache +# Staging site - restrict access +Order deny,allow +Deny from all +Allow from YOUR.OFFICE.IP +Allow from YOUR.HOME.IP + +# No search engine indexing +Header set X-Robots-Tag "noindex, nofollow" + +# Show errors (development) +php_flag display_errors on +php_value error_reporting 32767 +``` + +### Testing After Configuration + +```bash +# Test headers +curl -I https://yourdomain.com | grep -E "X-Frame|Cache-Control|X-Content" + +# Test specific file +curl -I https://yourdomain.com/wp-content/uploads/2024/12/image.jpg | grep Cache + +# Test PHP settings +echo '' > /home/yourdomain.com/public_html/info.php +curl https://yourdomain.com/info.php | grep memory_limit + +# Clean up +rm /home/yourdomain.com/public_html/info.php +``` + +--- + +## Real-World Examples + +### Example 1: High-Performance WordPress + +```apache +# Security +Header set X-Frame-Options "SAMEORIGIN" +Header set X-Content-Type-Options "nosniff" +Header set X-XSS-Protection "1; mode=block" +Header set Referrer-Policy "strict-origin-when-cross-origin" +Header unset Server +Header unset X-Powered-By + +# Brute Force Protection +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction throttle +BruteForceXForwardedFor On + +# Aggressive Caching + + Header set Cache-Control "max-age=31536000, public, immutable" + Header unset ETag + + + + Header set Cache-Control "max-age=31536000, public, immutable" + Header set Access-Control-Allow-Origin "*" + + + + Header set Cache-Control "max-age=2592000, public" + + +# PHP Optimization +php_value memory_limit 256M +php_value max_execution_time 300 +php_value upload_max_filesize 64M +php_value post_max_size 64M +php_flag display_errors off +php_flag log_errors on +``` + +### Example 2: WooCommerce E-commerce + +```apache +# Security Headers +Header set X-Frame-Options "SAMEORIGIN" +Header set X-Content-Type-Options "nosniff" +Header set X-XSS-Protection "1; mode=block" + +# Strict Brute Force Protection +BruteForceProtection On +BruteForceAllowedAttempts 3 +BruteForceWindow 900 +BruteForceAction block +BruteForceProtectPath /my-account/ +BruteForceProtectPath /checkout/ + +# Product Image Caching + + Header set Cache-Control "max-age=31536000, public, immutable" + + +# Don't Cache Checkout/Cart + + Header set Cache-Control "no-cache, no-store, must-revalidate" + + +# PHP for WooCommerce +php_value memory_limit 512M +php_value max_execution_time 600 +php_value max_input_vars 10000 +php_value upload_max_filesize 128M +php_value post_max_size 128M +``` + +### Example 3: API Server + +```apache +# CORS for API +Header set Access-Control-Allow-Origin "*" +Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" +Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key" +Header set Access-Control-Max-Age "86400" + +# JSON Response Headers + + Header set Content-Type "application/json; charset=utf-8" + Header set X-Content-Type-Options "nosniff" + Header set Cache-Control "no-cache, must-revalidate" + + +# API Rate Limiting +BruteForceProtection On +BruteForceAllowedAttempts 100 +BruteForceWindow 60 +BruteForceAction throttle +BruteForceProtectPath /api/ + +# Environment +SetEnv API_VERSION v2 +SetEnv API_ENVIRONMENT production +``` + +### Example 4: Static Site with CDN + +```apache +# Aggressive Caching +ExpiresActive On +ExpiresByType image/jpeg A31557600 +ExpiresByType image/png A31557600 +ExpiresByType image/gif A31557600 +ExpiresByType text/css A31557600 +ExpiresByType application/javascript A31557600 +ExpiresByType text/html A3600 + +# CORS for CDN +Header set Access-Control-Allow-Origin "*" + +# Security Headers +Header set X-Frame-Options "SAMEORIGIN" +Header set X-Content-Type-Options "nosniff" +Header set Content-Security-Policy "default-src 'self' https://cdn.example.com" + +# Remove Server Info +Header unset Server +Header unset X-Powered-By +``` + +### Example 5: Multi-Environment Setup + +**Production (.htaccess):** +```apache +SetEnv APPLICATION_ENV production +php_flag display_errors off +php_flag log_errors on +BruteForceProtection On +BruteForceAction block +Header set X-Robots-Tag "index, follow" +``` + +**Staging (staging.example.com/.htaccess):** +```apache +SetEnv APPLICATION_ENV staging +php_flag display_errors on +BruteForceProtection On +BruteForceAction log +Header set X-Robots-Tag "noindex, nofollow" + +# IP Restriction +Order deny,allow +Deny from all +Allow from 203.0.113.50 +``` + +**Development (dev.example.com/.htaccess):** +```apache +SetEnv APPLICATION_ENV development +php_flag display_errors on +php_value error_reporting 32767 +BruteForceProtection Off +Header set X-Robots-Tag "noindex, nofollow" +``` + +--- + +## Troubleshooting + +### Common Issues + +#### 1. Directives Not Working + +**Symptoms:** Headers not appearing, PHP settings not applied. + +**Solutions:** + +```bash +# Check module is installed +ls -la /usr/local/lsws/modules/cyberpanel_ols.so +# Should show 147KB file + +# Check module is loaded in config +grep cyberpanel_ols /usr/local/lsws/conf/httpd_config.conf +# Should show: module cyberpanel_ols { + +# Restart OpenLiteSpeed +/usr/local/lsws/bin/lswsctrl restart + +# Check logs for errors +tail -50 /usr/local/lsws/logs/error.log +``` + +#### 2. .htaccess File Permissions + +**Symptoms:** 500 Internal Server Error + +**Solutions:** + +```bash +# Set correct permissions +chmod 644 /home/yourdomain.com/public_html/.htaccess + +# Set correct ownership +chown nobody:nogroup /home/yourdomain.com/public_html/.htaccess + +# Verify +ls -la /home/yourdomain.com/public_html/.htaccess +# Should show: -rw-r--r-- nobody nogroup +``` + +#### 3. Headers Not Showing + +**Symptoms:** `curl -I` doesn't show custom headers + +**Solutions:** + +```bash +# Clear browser cache +# Some headers are cached aggressively + +# Test with curl (bypasses cache) +curl -I https://yourdomain.com + +# Test specific file +curl -I https://yourdomain.com/test.jpg + +# Check if file exists +ls -la /home/yourdomain.com/public_html/test.jpg + +# Verify .htaccess syntax +cat /home/yourdomain.com/public_html/.htaccess +``` + +#### 4. PHP Directives Not Applied + +**Symptoms:** `phpinfo()` shows old values + +**Solutions:** + +```bash +# Verify using LSPHP (not PHP-FPM) +# CyberPanel uses LSPHP by default + +# Check if directive is allowed +# Some directives are PHP_INI_SYSTEM only + +# Create test file +echo '' > /home/yourdomain.com/public_html/info.php + +# Check value +curl https://yourdomain.com/info.php | grep memory_limit + +# Delete test file +rm /home/yourdomain.com/public_html/info.php +``` + +#### 5. Brute Force Protection Not Triggering + +**Symptoms:** Can submit unlimited login attempts + +**Solutions:** + +```bash +# Check shared memory directory +ls -la /dev/shm/ols/ +# Should show BFProt.shm and BFProt.lock + +# Create if missing +mkdir -p /dev/shm/ols +chmod 755 /dev/shm/ols + +# Check .htaccess syntax +grep BruteForce /home/yourdomain.com/public_html/.htaccess + +# Must be POST request to protected path +curl -X POST https://yourdomain.com/wp-login.php -d "log=test&pwd=test" + +# Check logs +grep BruteForce /usr/local/lsws/logs/error.log + +# Restart +/usr/local/lsws/bin/lswsctrl restart +``` + +#### 6. Access Control Allowing All + +**Symptoms:** IP restrictions not working + +**Solutions:** + +```bash +# Verify your actual IP +curl ifconfig.me + +# Check CIDR syntax +# 192.168.1.0/24 = 192.168.1.1 to 192.168.1.254 +# 10.0.0.0/8 = 10.0.0.0 to 10.255.255.255 + +# Check logs for access decisions +grep "cyberpanel_access" /usr/local/lsws/logs/error.log + +# Test with curl from different IP +curl -I https://yourdomain.com +# Should get 403 if not allowed +``` + +#### 7. Redirect Loop + +**Symptoms:** ERR_TOO_MANY_REDIRECTS + +**Solutions:** + +```bash +# Check for conflicting redirects +grep Redirect /home/yourdomain.com/public_html/.htaccess + +# Common mistake: +# BAD: Both redirects active +# Redirect 301 / https://example.com/ +# Redirect 301 / https://www.example.com/ + +# GOOD: Only one +Redirect 301 / https://www.example.com/ + +# Check WordPress settings +# wp-admin > Settings > General +# WordPress Address and Site Address must match +``` + +### Getting Help + +#### Enable Debug Logging + +```bash +# Edit OpenLiteSpeed config +nano /usr/local/lsws/conf/httpd_config.conf + +# Change Log Level to DEBUG +# Restart +/usr/local/lsws/bin/lswsctrl restart + +# Monitor logs +tail -f /usr/local/lsws/logs/error.log +``` + +#### Collect Information + +```bash +# Module version +ls -lh /usr/local/lsws/modules/cyberpanel_ols.so + +# OpenLiteSpeed version +/usr/local/lsws/bin/openlitespeed -v + +# Check .htaccess +cat /home/yourdomain.com/public_html/.htaccess + +# Recent logs +tail -100 /usr/local/lsws/logs/error.log + +# Test headers +curl -I https://yourdomain.com +``` + +#### Report Issue + +When reporting issues, include: + +1. **What you're trying to do** (which feature) +2. **.htaccess content** (sanitized) +3. **Expected behavior** vs **actual behavior** +4. **Error logs** (last 50 lines) +5. **Test results** (curl output) +6. **Module version** and **OpenLiteSpeed version** + +--- + +## Performance Optimization + +### Best Practices + +1. **Minimize .htaccess size** - Only include necessary directives +2. **Use FilesMatch carefully** - Each pattern adds regex overhead +3. **Prefer block over throttle** - Throttle holds connections longer +4. **Whitelist known IPs** - Skips brute force checks entirely +5. **Set long cache times** - Reduce server load + +### Benchmarks + +| Metric | Value | +|--------|-------| +| Overhead per request | < 1ms | +| Memory per cached .htaccess | ~2KB | +| Memory per tracked IP (brute force) | ~64 bytes | +| Cache invalidation | mtime-based (instant) | + +### Optimization Examples + +**Before (Slow):** +```apache +# Every request checks all patterns +Header set X-Custom "Value" +Header set X-Another "Value" +Header set X-More "Value" + + + Header set Cache-Control "max-age=3600" + +``` + +**After (Fast):** +```apache +# Only static assets checked + + Header set Cache-Control "max-age=31536000, public, immutable" + +``` + +--- + +## Appendix + +### Quick Reference + +#### Headers +```apache +Header set Name "Value" +Header unset Name +Header append Name "Value" +``` + +#### Access Control +```apache +Order deny,allow +Deny from all +Allow from 192.168.1.0/24 +``` + +#### Redirects +```apache +Redirect 301 /old /new +RedirectMatch 301 ^/blog/(.*)$ /news/$1 +``` + +#### PHP +```apache +php_value memory_limit 256M +php_flag display_errors off +``` + +#### Brute Force +```apache +BruteForceProtection On +BruteForceAllowedAttempts 5 +BruteForceWindow 300 +BruteForceAction throttle +``` + +### Common MIME Types + +``` +image/jpeg, image/png, image/gif, image/webp, image/svg+xml +text/css, text/html, text/javascript, text/plain +application/javascript, application/json, application/xml, application/pdf +font/ttf, font/woff, font/woff2 +``` + +### Time Duration Reference + +``` +1 minute = 60 +5 minutes = 300 +15 minutes = 900 +1 hour = 3600 +1 day = 86400 +1 week = 604800 +1 month = 2592000 +1 year = 31557600 +``` + +### IP CIDR Cheat Sheet + +``` +/32 = 1 IP (255.255.255.255) +/24 = 256 IPs (255.255.255.0) +/16 = 65,536 IPs (255.255.0.0) +/8 = 16,777,216 IPs (255.0.0.0) +``` + +--- + +## Support + +- **GitHub:** [github.com/usmannasir/cyberpanel_ols](https://github.com/usmannasir/cyberpanel_ols) +- **Community:** [community.cyberpanel.net](https://community.cyberpanel.net) + +--- + +**Document Version:** 1.0 +**Module Version:** 2.2.0 +**Last Updated:** December 28, 2025 + +--- + +*Thank you for using the CyberPanel OpenLiteSpeed Module!* diff --git a/install/install.py b/install/install.py index b257dddcd..8ce6adee3 100644 --- a/install/install.py +++ b/install/install.py @@ -1088,24 +1088,25 @@ class preFlightsChecks: self.stdOut(f"Detected platform: {platform}", 1) # Platform-specific URLs and checksums (OpenLiteSpeed v1.8.4.1 - v2.0.5 Static Build) + # Module Build Date: December 28, 2025 - v2.2.0 Brute Force with Progressive Throttle BINARY_CONFIGS = { 'rhel8': { 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel8-static', 'sha256': '6ce688a237615102cc1603ee1999b3cede0ff3482d31e1f65705e92396d34b3a', - 'module_url': None, # RHEL 8 doesn't have module (use RHEL 9 if needed) - 'module_sha256': None + 'module_url': 'https://cyberpanel.net/binaries/rhel8/cyberpanel_ols.so', + 'module_sha256': '7c33d89c7fbcd3ed7b0422fee3f49b5e041713c2c2b7316a5774f6defa147572' }, 'rhel9': { 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel9-static', - 'sha256': '90468fb38767505185013024678d9144ae13100d2355097657f58719d98fbbc4', - 'module_url': 'https://cyberpanel.net/cyberpanel_ols_x86_64_rhel.so', - 'module_sha256': '127227db81bcbebf80b225fc747b69cfcd4ad2f01cea486aa02d5c9ba6c18109' + 'sha256': '709093d99d5d3e789134c131893614968e17eefd9ade2200f811d9b076b2f02e', + 'module_url': 'https://cyberpanel.net/binaries/rhel9/cyberpanel_ols.so', + 'module_sha256': 'ae65337e2d13babc0c675bb4264d469daffa2efb7627c9bf39ac59e42e3ebede' }, 'ubuntu': { 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-ubuntu-static', 'sha256': '89aaf66474e78cb3c1666784e0e7a417550bd317e6ab148201bdc318d36710cb', - 'module_url': 'https://cyberpanel.net/cyberpanel_ols_x86_64_ubuntu.so', - 'module_sha256': 'e7734f1e6226c2a0a8e00c1f6534ea9f577df9081b046736a774b1c52c28e7e5' + 'module_url': 'https://cyberpanel.net/binaries/ubuntu/cyberpanel_ols.so', + 'module_sha256': '62978ede1f174dd2885e5227a3d9cc463d0c27acd77cfc23743d7309ee0c54ea' } } @@ -1471,7 +1472,9 @@ module cyberpanel_ols { """Change MySQL root password""" try: if self.remotemysql == 'OFF': - passwordCMD = "use mysql;DROP DATABASE IF EXISTS test;DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%%';GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '%s';flush privileges;" % (self.mysql_Root_password) + # Use ALTER USER syntax (compatible with MariaDB 10.4+ and MySQL 5.7+) + # GRANT ... IDENTIFIED BY is deprecated in MariaDB 10.4+ and removed in 10.11+ + passwordCMD = "use mysql;DROP DATABASE IF EXISTS test;DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%%';ALTER USER 'root'@'localhost' IDENTIFIED BY '%s';GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION;flush privileges;" % (self.mysql_Root_password) # Try socket authentication first (for fresh MariaDB installations) socket_commands = ['sudo mysql', 'sudo mariadb', 'sudo /usr/bin/mysql', 'sudo /usr/bin/mariadb'] diff --git a/mailServer/mailserverManager.py b/mailServer/mailserverManager.py index 4a48ae1d5..1fd984d07 100644 --- a/mailServer/mailserverManager.py +++ b/mailServer/mailserverManager.py @@ -30,7 +30,7 @@ import _thread try: from dns.models import Domains as dnsDomains from dns.models import Records as dnsRecords - from mailServer.models import Forwardings, Pipeprograms + from mailServer.models import Forwardings, Pipeprograms, CatchAllEmail from plogical.acl import ACLManager from plogical.dnsUtilities import DNS from loginSystem.models import Administrator @@ -2030,6 +2030,151 @@ protocol sieve { json_data = json.dumps(data_ret) return HttpResponse(json_data) + def fetchCatchAllConfig(self): + try: + userID = self.request.session['userID'] + currentACL = ACLManager.loadedACL(userID) + + if ACLManager.currentContextPermission(currentACL, 'emailForwarding') == 0: + return ACLManager.loadErrorJson('fetchStatus', 0) + + data = json.loads(self.request.body) + domain = data['domain'] + + admin = Administrator.objects.get(pk=userID) + if ACLManager.checkOwnership(domain, admin, currentACL) == 1: + pass + else: + return ACLManager.loadErrorJson() + + try: + domainObj = Domains.objects.get(domain=domain) + catchAll = CatchAllEmail.objects.get(domain=domainObj) + data_ret = { + 'status': 1, + 'fetchStatus': 1, + 'configured': 1, + 'destination': catchAll.destination, + 'enabled': catchAll.enabled + } + except CatchAllEmail.DoesNotExist: + data_ret = { + 'status': 1, + 'fetchStatus': 1, + 'configured': 0 + } + except Domains.DoesNotExist: + data_ret = { + 'status': 0, + 'fetchStatus': 0, + 'error_message': 'Domain not found in email system' + } + + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + except BaseException as msg: + data_ret = {'status': 0, 'fetchStatus': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + def saveCatchAllConfig(self): + try: + userID = self.request.session['userID'] + currentACL = ACLManager.loadedACL(userID) + + if ACLManager.currentContextPermission(currentACL, 'emailForwarding') == 0: + return ACLManager.loadErrorJson('saveStatus', 0) + + data = json.loads(self.request.body) + domain = data['domain'] + destination = data['destination'] + enabled = data.get('enabled', True) + + admin = Administrator.objects.get(pk=userID) + if ACLManager.checkOwnership(domain, admin, currentACL) == 1: + pass + else: + return ACLManager.loadErrorJson() + + # Validate destination email + if '@' not in destination: + data_ret = {'status': 0, 'saveStatus': 0, 'error_message': 'Invalid destination email address'} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + domainObj = Domains.objects.get(domain=domain) + + # Create or update catch-all config + catchAll, created = CatchAllEmail.objects.update_or_create( + domain=domainObj, + defaults={'destination': destination, 'enabled': enabled} + ) + + # Also add/update entry in Forwardings table for Postfix + catchAllSource = '@' + domain + if enabled: + # Remove existing catch-all forwarding if any + Forwardings.objects.filter(source=catchAllSource).delete() + # Add new forwarding + forwarding = Forwardings(source=catchAllSource, destination=destination) + forwarding.save() + else: + # Remove catch-all forwarding when disabled + Forwardings.objects.filter(source=catchAllSource).delete() + + data_ret = { + 'status': 1, + 'saveStatus': 1, + 'message': 'Catch-all email configured successfully' + } + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + except BaseException as msg: + data_ret = {'status': 0, 'saveStatus': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + def deleteCatchAllConfig(self): + try: + userID = self.request.session['userID'] + currentACL = ACLManager.loadedACL(userID) + + if ACLManager.currentContextPermission(currentACL, 'emailForwarding') == 0: + return ACLManager.loadErrorJson('deleteStatus', 0) + + data = json.loads(self.request.body) + domain = data['domain'] + + admin = Administrator.objects.get(pk=userID) + if ACLManager.checkOwnership(domain, admin, currentACL) == 1: + pass + else: + return ACLManager.loadErrorJson() + + domainObj = Domains.objects.get(domain=domain) + + # Delete catch-all config + CatchAllEmail.objects.filter(domain=domainObj).delete() + + # Remove from Forwardings table + catchAllSource = '@' + domain + Forwardings.objects.filter(source=catchAllSource).delete() + + data_ret = { + 'status': 1, + 'deleteStatus': 1, + 'message': 'Catch-all email removed successfully' + } + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + + except BaseException as msg: + data_ret = {'status': 0, 'deleteStatus': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + def refreshEmailDiskUsage(self): """Refresh disk usage for all email accounts in a domain""" try: diff --git a/mailServer/models.py b/mailServer/models.py index 96fa544da..b0765152f 100644 --- a/mailServer/models.py +++ b/mailServer/models.py @@ -49,3 +49,17 @@ class Transport(models.Model): class Pipeprograms(models.Model): source = models.CharField(max_length=80) destination = models.TextField() + + class Meta: + db_table = 'e_pipeprograms' + + +class CatchAllEmail(models.Model): + """Stores catch-all email configuration per domain""" + domain = models.OneToOneField(Domains, on_delete=models.CASCADE, primary_key=True, db_column='domain_id') + destination = models.CharField(max_length=255) + enabled = models.BooleanField(default=True) + + class Meta: + db_table = 'e_catchall' + managed = False diff --git a/mailServer/urls.py b/mailServer/urls.py index 29d31a7d0..c57c22377 100644 --- a/mailServer/urls.py +++ b/mailServer/urls.py @@ -38,4 +38,9 @@ urlpatterns = [ ### disk usage refresh re_path(r'^refreshEmailDiskUsage$', views.refreshEmailDiskUsage, name='refreshEmailDiskUsage'), + + ## Catch-All Email + re_path(r'^fetchCatchAllConfig$', views.fetchCatchAllConfig, name='fetchCatchAllConfig'), + re_path(r'^saveCatchAllConfig$', views.saveCatchAllConfig, name='saveCatchAllConfig'), + re_path(r'^deleteCatchAllConfig$', views.deleteCatchAllConfig, name='deleteCatchAllConfig'), ] diff --git a/mailServer/views.py b/mailServer/views.py index 579ed9076..dd1774e3a 100644 --- a/mailServer/views.py +++ b/mailServer/views.py @@ -262,6 +262,33 @@ def SaveEmailLimitsNew(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) +def fetchCatchAllConfig(request): + try: + msM = MailServerManager(request) + return msM.fetchCatchAllConfig() + except KeyError as msg: + data_ret = {'fetchStatus': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + +def saveCatchAllConfig(request): + try: + msM = MailServerManager(request) + return msM.saveCatchAllConfig() + except KeyError as msg: + data_ret = {'saveStatus': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + +def deleteCatchAllConfig(request): + try: + msM = MailServerManager(request) + return msM.deleteCatchAllConfig() + except KeyError as msg: + data_ret = {'deleteStatus': 0, 'error_message': str(msg)} + json_data = json.dumps(data_ret) + return HttpResponse(json_data) + def refreshEmailDiskUsage(request): try: msM = MailServerManager(request) diff --git a/plogical/DockerSites.py b/plogical/DockerSites.py index 4e6c8f12d..6c3603ea4 100644 --- a/plogical/DockerSites.py +++ b/plogical/DockerSites.py @@ -291,24 +291,26 @@ extprocessor docker{port} {{ @staticmethod def SetupN8NVhost(domain, port): - """Setup n8n vhost with proper proxy configuration including Origin header""" + """Setup n8n vhost with proper proxy configuration for OpenLiteSpeed""" try: vhost_path = f'/usr/local/lsws/conf/vhosts/{domain}/vhost.conf' - + if not os.path.exists(vhost_path): logging.writeToFile(f"Error: Vhost file not found at {vhost_path}") return False - + # Read existing vhost configuration with open(vhost_path, 'r') as f: content = f.read() - + # Check if context already exists if 'context / {' in content: logging.writeToFile("Context already exists, skipping...") return True - + # Add proxy context with proper headers for n8n + # NOTE: Do NOT include "RequestHeader set Origin" - OpenLiteSpeed cannot override + # browser Origin headers, which is why NODE_ENV=development is required proxy_context = f''' # N8N Proxy Configuration @@ -322,7 +324,6 @@ context / {{ RequestHeader set X-Forwarded-For $ip RequestHeader set X-Forwarded-Proto https RequestHeader set X-Forwarded-Host "{domain}" - RequestHeader set Origin "{domain}, {domain}" RequestHeader set Host "{domain}" END_extraHeaders }} @@ -1370,7 +1371,7 @@ services: 'DB_POSTGRESDB_PASSWORD': self.data['MySQLPassword'], 'N8N_HOST': '0.0.0.0', 'N8N_PORT': '5678', - 'NODE_ENV': 'production', + 'NODE_ENV': 'development', # Required for OpenLiteSpeed compatibility - OLS cannot override browser Origin headers which n8n v1.87.0+ validates in production mode 'N8N_EDITOR_BASE_URL': f"https://{self.data['finalURL']}", 'WEBHOOK_URL': f"https://{self.data['finalURL']}", 'WEBHOOK_TUNNEL_URL': f"https://{self.data['finalURL']}", diff --git a/plogical/modSec.py b/plogical/modSec.py index be676c89e..9aed99228 100644 --- a/plogical/modSec.py +++ b/plogical/modSec.py @@ -18,6 +18,102 @@ class modSec: tempRulesFile = "/home/cyberpanel/tempModSecRules" mirrorPath = "cyberpanel.net" + # Compatible ModSecurity binaries (built against custom OLS headers) + # These prevent ABI incompatibility crashes (Signal 11/SIGSEGV) + MODSEC_COMPATIBLE = { + 'rhel8': { + 'url': 'https://cyberpanel.net/mod_security-compatible-rhel8.so', + 'sha256': 'bbbf003bdc7979b98f09b640dffe2cbbe5f855427f41319e4c121403c05837b2' + }, + 'rhel9': { + 'url': 'https://cyberpanel.net/mod_security-compatible-rhel.so', + 'sha256': '19deb2ffbaf1334cf4ce4d46d53f747a75b29e835bf5a01f91ebcc0c78e98629' + }, + 'ubuntu': { + 'url': 'https://cyberpanel.net/mod_security-compatible-ubuntu.so', + 'sha256': 'ed02c813136720bd4b9de5925f6e41bdc8392e494d7740d035479aaca6d1e0cd' + } + } + + @staticmethod + def detectPlatform(): + """Detect OS platform for compatible binary selection""" + try: + # Check for Ubuntu/Debian + if os.path.exists('/etc/lsb-release'): + with open('/etc/lsb-release', 'r') as f: + content = f.read() + if 'Ubuntu' in content or 'ubuntu' in content: + return 'ubuntu' + + # Check for Debian + if os.path.exists('/etc/debian_version'): + return 'ubuntu' # Use Ubuntu binary for Debian + + # Check for RHEL-based distributions + if os.path.exists('/etc/os-release'): + with open('/etc/os-release', 'r') as f: + content = f.read().lower() + + # Check for version 8.x + if 'version="8.' in content or 'version_id="8' in content: + return 'rhel8' + + # Check for version 9.x + if 'version="9.' in content or 'version_id="9' in content: + return 'rhel9' + + return 'rhel9' # Default to rhel9 + except: + return 'rhel9' + + @staticmethod + def downloadCompatibleModSec(platform): + """Download and install compatible ModSecurity binary""" + try: + config = modSec.MODSEC_COMPATIBLE.get(platform) + if not config: + logging.CyberCPLogFileWriter.writeToFile(f"No compatible ModSecurity for platform {platform}") + return False + + modsec_path = "/usr/local/lsws/modules/mod_security.so" + tmp_path = "/tmp/mod_security-compatible.so" + + # Download compatible binary + command = f"wget -q {config['url']} -O {tmp_path}" + result = subprocess.call(shlex.split(command)) + if result != 0: + logging.CyberCPLogFileWriter.writeToFile("Failed to download compatible ModSecurity") + return False + + # Verify checksum + import hashlib + sha256_hash = hashlib.sha256() + with open(tmp_path, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + actual_sha256 = sha256_hash.hexdigest() + + if actual_sha256 != config['sha256']: + logging.CyberCPLogFileWriter.writeToFile(f"ModSecurity checksum mismatch: expected {config['sha256']}, got {actual_sha256}") + os.remove(tmp_path) + return False + + # Backup original if exists + if os.path.exists(modsec_path): + shutil.copy2(modsec_path, f"{modsec_path}.stock") + + # Install compatible version + shutil.move(tmp_path, modsec_path) + os.chmod(modsec_path, 0o644) + + logging.CyberCPLogFileWriter.writeToFile("Installed compatible ModSecurity binary") + return True + + except BaseException as msg: + logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [downloadCompatibleModSec]") + return False + @staticmethod def installModSec(): try: @@ -45,6 +141,23 @@ class modSec: writeToFile.writelines("ModSecurity Installed.[200]\n") writeToFile.close() + # Check if custom OLS binary is installed - if so, replace with compatible ModSecurity + custom_ols_marker = "/usr/local/lsws/modules/cyberpanel_ols.so" + if os.path.exists(custom_ols_marker): + writeToFile = open(modSec.installLogPath, 'a') + writeToFile.writelines("Custom OLS detected, installing compatible ModSecurity...\n") + writeToFile.close() + + platform = modSec.detectPlatform() + if modSec.downloadCompatibleModSec(platform): + writeToFile = open(modSec.installLogPath, 'a') + writeToFile.writelines("Compatible ModSecurity installed successfully.\n") + writeToFile.close() + else: + writeToFile = open(modSec.installLogPath, 'a') + writeToFile.writelines("WARNING: Could not install compatible ModSecurity. May experience crashes.\n") + writeToFile.close() + return 1 except BaseException as msg: logging.CyberCPLogFileWriter.writeToFile(str(msg) + "[installModSec]") diff --git a/plogical/upgrade.py b/plogical/upgrade.py index bd7d19a6c..0915aaa3a 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -938,25 +938,32 @@ class Upgrade: platform = Upgrade.detectPlatform() Upgrade.stdOut(f"Detected platform: {platform}", 0) - # Platform-specific URLs and checksums (OpenLiteSpeed v1.8.4.1 - v2.0.5 Static Build) + # Platform-specific URLs and checksums (OpenLiteSpeed v1.8.4.1 with PHPConfig + Header unset fix + Static Linking) + # Module Build Date: December 28, 2025 - v2.2.0 Brute Force with Progressive Throttle BINARY_CONFIGS = { 'rhel8': { 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel8-static', 'sha256': '6ce688a237615102cc1603ee1999b3cede0ff3482d31e1f65705e92396d34b3a', - 'module_url': None, # RHEL 8 doesn't have module (use RHEL 9 if needed) - 'module_sha256': None + 'module_url': 'https://cyberpanel.net/binaries/rhel8/cyberpanel_ols.so', + 'module_sha256': '7c33d89c7fbcd3ed7b0422fee3f49b5e041713c2c2b7316a5774f6defa147572', + 'modsec_url': 'https://cyberpanel.net/mod_security-compatible-rhel8.so', + 'modsec_sha256': 'bbbf003bdc7979b98f09b640dffe2cbbe5f855427f41319e4c121403c05837b2' }, 'rhel9': { 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-rhel9-static', - 'sha256': '90468fb38767505185013024678d9144ae13100d2355097657f58719d98fbbc4', - 'module_url': 'https://cyberpanel.net/cyberpanel_ols_x86_64_rhel.so', - 'module_sha256': '127227db81bcbebf80b225fc747b69cfcd4ad2f01cea486aa02d5c9ba6c18109' + 'sha256': '709093d99d5d3e789134c131893614968e17eefd9ade2200f811d9b076b2f02e', + 'module_url': 'https://cyberpanel.net/binaries/rhel9/cyberpanel_ols.so', + 'module_sha256': 'ae65337e2d13babc0c675bb4264d469daffa2efb7627c9bf39ac59e42e3ebede', + 'modsec_url': 'https://cyberpanel.net/mod_security-compatible-rhel.so', + 'modsec_sha256': '19deb2ffbaf1334cf4ce4d46d53f747a75b29e835bf5a01f91ebcc0c78e98629' }, 'ubuntu': { 'url': 'https://cyberpanel.net/openlitespeed-phpconfig-x86_64-ubuntu-static', 'sha256': '89aaf66474e78cb3c1666784e0e7a417550bd317e6ab148201bdc318d36710cb', - 'module_url': 'https://cyberpanel.net/cyberpanel_ols_x86_64_ubuntu.so', - 'module_sha256': 'e7734f1e6226c2a0a8e00c1f6534ea9f577df9081b046736a774b1c52c28e7e5' + 'module_url': 'https://cyberpanel.net/binaries/ubuntu/cyberpanel_ols.so', + 'module_sha256': '62978ede1f174dd2885e5227a3d9cc463d0c27acd77cfc23743d7309ee0c54ea', + 'modsec_url': 'https://cyberpanel.net/mod_security-compatible-ubuntu.so', + 'modsec_sha256': 'ed02c813136720bd4b9de5925f6e41bdc8392e494d7740d035479aaca6d1e0cd' } } @@ -970,8 +977,11 @@ class Upgrade: OLS_BINARY_SHA256 = config['sha256'] MODULE_URL = config['module_url'] MODULE_SHA256 = config['module_sha256'] + MODSEC_URL = config.get('modsec_url') + MODSEC_SHA256 = config.get('modsec_sha256') OLS_BINARY_PATH = "/usr/local/lsws/bin/openlitespeed" MODULE_PATH = "/usr/local/lsws/modules/cyberpanel_ols.so" + MODSEC_PATH = "/usr/local/lsws/modules/mod_security.so" # Create backup from datetime import datetime @@ -983,12 +993,16 @@ class Upgrade: if os.path.exists(OLS_BINARY_PATH): shutil.copy2(OLS_BINARY_PATH, f"{backup_dir}/openlitespeed.backup") Upgrade.stdOut(f"Backup created at: {backup_dir}", 0) + # Also backup existing ModSecurity if it exists + if os.path.exists(MODSEC_PATH): + shutil.copy2(MODSEC_PATH, f"{backup_dir}/mod_security.so.backup") except Exception as e: Upgrade.stdOut(f"WARNING: Could not create backup: {e}", 0) # Download binaries to temp location tmp_binary = "/tmp/openlitespeed-custom" tmp_module = "/tmp/cyberpanel_ols.so" + tmp_modsec = "/tmp/mod_security.so" Upgrade.stdOut("Downloading custom binaries...", 0) @@ -1026,6 +1040,18 @@ class Upgrade: else: Upgrade.stdOut("Note: No CyberPanel module for this platform", 0) + # Download compatible ModSecurity if existing ModSecurity is installed + # This prevents ABI incompatibility crashes (Signal 11/SIGSEGV) + modsec_downloaded = False + if os.path.exists(MODSEC_PATH) and MODSEC_URL and MODSEC_SHA256: + Upgrade.stdOut("Existing ModSecurity detected - downloading compatible version...", 0) + if Upgrade.downloadCustomBinary(MODSEC_URL, tmp_modsec, MODSEC_SHA256): + modsec_downloaded = True + else: + Upgrade.stdOut("WARNING: Failed to download compatible ModSecurity", 0) + Upgrade.stdOut("ModSecurity may crash due to ABI incompatibility", 0) + Upgrade.stdOut("Consider manually updating ModSecurity after upgrade", 0) + # Install OpenLiteSpeed binary Upgrade.stdOut("Installing custom binaries...", 0) @@ -1068,6 +1094,16 @@ class Upgrade: Upgrade.stdOut(f"ERROR: Failed to install module: {e}", 0) return False + # Install compatible ModSecurity (if downloaded) + if modsec_downloaded: + try: + shutil.move(tmp_modsec, MODSEC_PATH) + os.chmod(MODSEC_PATH, 0o644) + Upgrade.stdOut("Installed compatible ModSecurity module", 0) + except Exception as e: + Upgrade.stdOut(f"WARNING: Failed to install ModSecurity: {e}", 0) + # Non-fatal, continue + # Verify installation if os.path.exists(OLS_BINARY_PATH): if not module_downloaded or os.path.exists(MODULE_PATH): @@ -2495,6 +2531,19 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL except: pass + # Email Filtering Tables - Catch-All, Plus-Addressing, Pattern Forwarding + query = """CREATE TABLE IF NOT EXISTS `e_catchall` ( + `domain_id` varchar(50) NOT NULL, + `destination` varchar(255) NOT NULL, + `enabled` tinyint(1) NOT NULL DEFAULT 1, + PRIMARY KEY (`domain_id`), + CONSTRAINT `fk_catchall_domain` FOREIGN KEY (`domain_id`) REFERENCES `e_domains` (`domain`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4""" + try: + cursor.execute(query) + except: + pass + try: connection.close() except: