Skip to content

NectarPress Server Configuration Guide

Correct server configuration is responsible for 30–50% of your Lighthouse Performance score. These settings are verified on Apache 2.4 + PHP 8.x and Nginx 1.24.


Apache .htaccess

Place this inside <IfModule mod_rewrite.c> in your WordPress .htaccess, after the standard WordPress rewrite block.

# ── Browser Caching ─────────────────────────────────────────────
<IfModule mod_expires.c>
    ExpiresActive On

    # Fonts — 1 year (immutable, hashed filenames)
    ExpiresByType font/woff2                        "access plus 1 year"
    ExpiresByType font/woff                         "access plus 1 year"
    ExpiresByType application/font-woff             "access plus 1 year"

    # Theme CSS + JS (Vite hashed filenames, safe to cache 1 year)
    ExpiresByType text/css                          "access plus 1 year"
    ExpiresByType application/javascript            "access plus 1 year"
    ExpiresByType text/javascript                   "access plus 1 year"

    # Images
    ExpiresByType image/webp                        "access plus 1 year"
    ExpiresByType image/avif                        "access plus 1 year"
    ExpiresByType image/jpeg                        "access plus 1 month"
    ExpiresByType image/png                         "access plus 1 month"
    ExpiresByType image/svg+xml                     "access plus 1 month"
    ExpiresByType image/gif                         "access plus 1 month"
    ExpiresByType image/x-icon                      "access plus 1 year"

    # HTML — always revalidate
    ExpiresByType text/html                         "access plus 0 seconds"
</IfModule>

# ── Cache-Control Headers ────────────────────────────────────────
<IfModule mod_headers.c>
    # Hashed assets — immutable (Vite appends content hash to filename)
    <FilesMatch "\.(js|css|woff2|woff)$">
        Header set Cache-Control "max-age=31536000, immutable"
    </FilesMatch>
    # Images
    <FilesMatch "\.(jpg|jpeg|png|gif|webp|avif|svg|ico)$">
        Header set Cache-Control "max-age=2592000, public"
    </FilesMatch>
</IfModule>

# ── Gzip Compression ─────────────────────────────────────────────
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
    AddOutputFilterByType DEFLATE application/javascript text/javascript
    AddOutputFilterByType DEFLATE application/json application/ld+json
    AddOutputFilterByType DEFLATE image/svg+xml application/xml
    AddOutputFilterByType DEFLATE font/woff font/woff2

    # Serve pre-compressed files if available
    RewriteEngine On
    RewriteCond %{HTTP:Accept-Encoding} gzip
    RewriteCond %{REQUEST_FILENAME}.gz -f
    RewriteRule ^(.*)$ $1.gz [QSA,L]
</IfModule>

# ── Brotli (requires mod_brotli) ─────────────────────────────────
<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css
    AddOutputFilterByType BROTLI_COMPRESS application/javascript text/javascript
    AddOutputFilterByType BROTLI_COMPRESS application/json application/ld+json
    AddOutputFilterByType BROTLI_COMPRESS font/woff2 image/svg+xml
</IfModule>

# ── Security Headers ─────────────────────────────────────────────
<IfModule mod_headers.c>
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
    # HSTS — enable ONLY after verifying HTTPS works perfectly on your site
    # Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>

# ── Prevent .php file access in uploads ──────────────────────────
<IfModule mod_rewrite.c>
    RewriteRule ^wp-content/uploads/.*\.php$ - [F,NC]
</IfModule>

Nginx nginx.conf / site config

Add inside your server {} block:

# ── Browser caching ──────────────────────────────────────────────
location ~* \.(woff2|woff|js|css)$ {
    expires 1y;
    add_header Cache-Control "max-age=31536000, immutable";
    add_header Vary "Accept-Encoding";
    gzip_static on;
    brotli_static on;
}

location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico)$ {
    expires 30d;
    add_header Cache-Control "max-age=2592000, public";
    gzip_static on;
}

# HTML — always revalidate
location ~* \.html$ {
    add_header Cache-Control "no-cache, must-revalidate";
    add_header Pragma "no-cache";
}

# ── Gzip compression ─────────────────────────────────────────────
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
    text/plain text/css text/xml text/javascript
    application/json application/javascript application/ld+json
    image/svg+xml font/woff font/woff2 application/xml;

# ── Brotli compression (requires ngx_brotli module) ──────────────
brotli on;
brotli_comp_level 6;
brotli_types
    text/plain text/css application/javascript
    application/json application/ld+json image/svg+xml;

# ── Security headers ─────────────────────────────────────────────
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

# ── Block PHP in uploads ──────────────────────────────────────────
location ~* /wp-content/uploads/.*\.php$ {
    deny all;
}

Cloudflare Page Rules

If you use Cloudflare CDN (recommended for Nepali traffic — edge nodes in Mumbai/Singapore):

URL Pattern Cache Level Edge TTL
*.nectarpress.com/wp-content/themes/nectarpress/assets/* Cache Everything 1 year
*.nectarpress.com/wp-content/uploads/* Cache Everything 30 days
*.nectarpress.com/?* Bypass
*.nectarpress.com/wp-admin/* Bypass
*.nectarpress.com/wp-login.php Bypass

Important: Always bypass cache for: - URLs with query parameters (?) - Admin paths (/wp-admin/) - Logged-in users (set via Cache-Control: private — NectarPress does this automatically for premium readers)


PHP Configuration Recommendations

; php.ini or .user.ini
memory_limit = 256M          ; theme runs cleanly in 40M; 256M for import operations
max_execution_time = 300     ; allow for demo import (large packs)
upload_max_filesize = 64M    ; ePaper PDFs can be large
post_max_size = 64M
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 4000
opcache.revalidate_freq = 60

Hosting Recommendations for Nepali Traffic

Tier Provider Nepal latency Notes
Shared Himalayan Host ~30ms Nepal-based, best latency
VPS DigitalOcean Bangalore ~70ms Good balance, SSD
VPS Vultr Mumbai ~65ms Similar to DO
Managed WP Cloudways (Bangalore) ~70ms Easy scaling
Enterprise AWS ap-south-1 (Mumbai) ~60ms With CloudFront → ~20ms

Always add Cloudflare CDN regardless of host — Nepali peak hours (7–10 PM NST) can bring 10× traffic spikes during breaking news events. Cloudflare's edge absorbs this without hitting your origin.