diff --git a/CHANGELOG.md b/CHANGELOG.md index dc07931..1566753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +- **The Nginx guides**, now two new posts and one existing post on wpvilla.in: [serving WebP and AVIF through Accept-header negotiation](https://wpvilla.in/serve-webp-avif-nginx-trellis-accept-header-bedrock-uploads/), [browser caching headers as a global include](https://wpvilla.in/nginx-browser-caching-headers-trellis-all-includes-expires/) and [the redirect provision tag and query string](https://wpvilla.in/trellis-nginx-redirect-not-deployed-query-string-dropped/). The four READMEs under `docs/nginx/` are gone; the `.conf.j2` templates stay, and each now opens with a link to its post. The WebP/AVIF template is now `docs/nginx/image-optimization/nginx-includes/all/00-webp-avif.conf.j2`, rewritten after testing it against Nginx: the old file failed `nginx -t` as a site include (`map` is not allowed in a server block), served `photo.webp` instead of the documented `photo.jpg.webp`, and lost to the caching include's image block. It is now a global include that loads first, uses `set`/`if` instead of `map`, and sets its own expiry; rename existing `photo.webp` files to `photo.jpg.webp`. Site includes live in a folder named after the site key; the guides' `nginx_includes` setting does not exist. The browser caching guide had documented 30-day and 7-day durations while the template sets one year; the post describes the template. The generic redirect best-practice and rollback sections are not carried over. - **The troubleshooting guides**, now covered on wpvilla.in: [PHP-FPM memory exhaustion](https://wpvilla.in/debugging-php-fpm-memory-exhaustion-wordpress-woocommerce-trellis/), [mail that never arrives or bounces](https://wpvilla.in/trellis-mail-not-sending-smtp-config-reset-and-bounces/), [the MariaDB failure at "Set root user password"](https://wpvilla.in/trellis-provision-set-root-user-password-fails-mariadb-provider-plugins/) and a new post on [the OOM killer, cron and the unlimited PHP CLI memory limit](https://wpvilla.in/linux-oom-killer-wordpress-cron-php-cli-memory-limit-trellis/). `docs/troubleshooting/` is gone, and the README, `AGENTS.md` and `CLAUDE.md` no longer point at it. The generic MariaDB diagnostics and the PHP-FPM sizing tables are not carried over; they stay in git history. - **The security guides**, now four posts on wpvilla.in: [the two scanners and checksum verification](https://wpvilla.in/wordpress-malware-scanners-targeted-general-checksum-verification/), [reading scan results](https://wpvilla.in/reading-malware-scan-results-false-positives-wordpress-hardening/), [a permanent Nginx deny list](https://wpvilla.in/nginx-deny-list-trellis-cidr-whitelist-self-lockout/) and [a Trellis security routine](https://wpvilla.in/trellis-wordpress-security-routine-abuseipdb-fail2ban/), plus the existing [fail2ban post](https://wpvilla.in/fail2ban-wordpress-wp-login-trellis-nginx-deny-list/). The `README`, `SECURITY-GUIDE` and `SCANNER-SUMMARY` guides in `wp-cli/security/` and the `README`, `FAIL2BAN` and `MANUAL-IP-BLOCKING` guides in `trellis/security/` are gone; the scanners and the AbuseIPDB scripts stay. The scanner wrapper's closing hint now names the post. - **The provisioning and updater guides**, now four posts on wpvilla.in: [a new Mac for Trellis](https://wpvilla.in/new-mac-trellis-development-setup-lima-composer-node/), [cloning an existing project](https://wpvilla.in/clone-trellis-bedrock-project-lima-vm-first-provision/), [updating Trellis](https://wpvilla.in/update-trellis-without-losing-customisations-rsync-excludes/) and [provision tags and the PHP upgrade](https://wpvilla.in/trellis-provision-tags-php-version-upgrade-checklist/). The cron guide is covered by the existing [WP-Cron interval post](https://wpvilla.in/trellis-wp-cron-interval-scheduled-posts-late-cron-interval/). `trellis/provision/` is gone, as are the updater README and `manual-update.md`; the `trellis-updater` script stays. diff --git a/CLAUDE.md b/CLAUDE.md index e47bc14..3a33577 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,7 @@ This is a collection of tools, scripts, and documentation for WordPress operatio runnable commands and so have no CLI category of their own: - `docs/dev/` - Design and planning docs for wp-ops itself. New ones go here, not at the `docs/` top level - `docs/cli/` - How the `wp-ops` binary works: usage reference (categories, platforms, completion, guide search) and the trellis-cli plugin - - `docs/nginx/` - Web server configs: `browser-caching/`, `image-optimization/`, `redirects/` (guides plus the `.conf.j2` templates they describe) + - `docs/nginx/` - Web server configs: `browser-caching/`, `image-optimization/`, `redirects/` (the `.conf.j2` templates; each header links its wpvilla.in post) - `docs/bedrock/` - Bedrock/Composer workflows: local package development, and the standard `wp-cli.yml` for Bedrock path setup - `docs/wordpress-utilities/` - Copy-paste-into-a-project components and reference material: `snippets/`, `speed-optimization/` @@ -251,9 +251,10 @@ When updating post content via WP-CLI: ### Nginx Configuration Custom Nginx includes (browser caching, image optimization) must be: -1. Copied to Trellis `nginx-includes/` directory -2. Referenced in `wordpress_sites.*.nginx_includes` configuration -3. Applied via `trellis provision ` +1. Copied to a Trellis `nginx-includes/` directory: `all/` for every site, or a folder named after the site key in `wordpress_sites.yml` for one site (there is no `nginx_includes` setting; Trellis only reads those folders) +2. Applied via `trellis provision --tags nginx-includes ` + +Includes are read inside the server block in alphabetical order, so `map` is not allowed there and the first matching regex `location` wins. ## Development Workflow diff --git a/README.md b/README.md index fb29dc8..49ad82a 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ sites are run with: | **Monitoring** | AI-crawler traffic, error surfacing, traffic by country, cron-scheduled reports | [→](trellis/monitoring/README.md) | | **Security** | Malware scanners with checksum verification, fail2ban, manual IP blocking, AbuseIPDB lookup | [routine](https://wpvilla.in/trellis-wordpress-security-routine-abuseipdb-fail2ban/) · [scanners](https://wpvilla.in/wordpress-malware-scanners-targeted-general-checksum-verification/) | | **Content & SEO** | Page creation, block pattern validation, redirect/schema/orphan-page audits | [SEO](wp-cli/seo/README.md) | -| **Nginx** | WebP/AVIF serving, browser caching and redirect templates for Trellis `nginx-includes/` | [images](docs/nginx/image-optimization/README.md) · [caching](docs/nginx/browser-caching/README.md) · [redirects](docs/nginx/redirects/README.md) | +| **Nginx** | WebP/AVIF serving, browser caching and redirect templates for Trellis `nginx-includes/` [images](https://wpvilla.in/serve-webp-avif-nginx-trellis-accept-header-bedrock-uploads/) · [caching](https://wpvilla.in/nginx-browser-caching-headers-trellis-all-includes-expires/) · [redirects](https://wpvilla.in/trellis-nginx-redirect-not-deployed-query-string-dropped/) | | **Bedrock** | Testing unreleased packages via Composer `path` repos, standard `wp-cli.yml` | [packages](docs/bedrock/local-package-development/README.md) · [wp-cli.yml](docs/bedrock/wp-cli-config/README.md) | | **Scripts & guides** | Theme sync, PR helpers, WP-CLI snippets for Bedrock | [scripts](scripts/README.md) · [snippets](docs/wordpress-utilities/snippets/README.md) | diff --git a/docs/nginx/README.md b/docs/nginx/README.md deleted file mode 100644 index 9ca3a6e..0000000 --- a/docs/nginx/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Nginx Configuration Tools - -Nginx include templates for WordPress sites on [Roots Trellis](https://roots.io/trellis/). Each guide below covers one template, how to wire it into `wordpress_sites.yml`, and how to check it deployed. - -| Guide | What it deploys | -|-------|-----------------| -| [browser-caching/](browser-caching/README.md) | Cache headers for static assets (global include) | -| [image-optimization/](image-optimization/README.md) | Automatic WebP/AVIF content negotiation (site include) | -| [redirects/](redirects/README.md) | SEO redirect templates and examples (site include) | - -## Deploying an include - -1. **Copy the template** into your Trellis project: - ```bash - # Site-specific include - cp docs/nginx/image-optimization/nginx-includes/webp-avf.conf.j2 \ - ~/trellis/nginx-includes/ - - # Global include (applies to every site on the server) - cp docs/nginx/browser-caching/nginx-includes/all/assets-expiry.conf.j2 \ - ~/trellis/nginx-includes/all/ - ``` - -2. **Reference site-specific includes** in `group_vars/production/wordpress_sites.yml`. Files under `nginx-includes/all/` need no entry: - ```yaml - wordpress_sites: - example.com: - nginx_includes: - - nginx-includes/webp-avf.conf.j2 - - nginx-includes/seo-redirects.conf.j2 - ``` - -3. **Try it in the VM first**, then deploy with the `nginx-includes` tag: - ```bash - trellis provision development - trellis vm shell -- sudo nginx -t - - trellis provision --tags nginx-includes production - ``` - -4. **Verify** with `curl -I` against a real URL (each guide lists the expected headers). - -## Scopes - -| Directory in your Trellis project | Lands on the server at | Use for | -|-----------------------------------|------------------------|---------| -| `nginx-includes/all/` | `/etc/nginx/includes.d/all/` | Caching headers, security headers, rate limits: anything every site wants | -| `nginx-includes/` | `/etc/nginx/includes.d/example.com/` | Image negotiation, per-site redirects, custom rewrites | - -All templates use the `.conf.j2` extension, so Trellis variables such as `{{ site_name }}` and Jinja2 conditionals work. Trellis validates the syntax and reloads Nginx after provisioning. - -## Troubleshooting - -1. **Configuration not applied**: confirm the file sits in the right `nginx-includes/` directory, that `wordpress_sites.yml` names it, and re-provision with `--tags nginx-includes`. -2. **Syntax errors**: run `sudo nginx -t` on the server, check the `.conf.j2` for typos and Jinja2 mistakes, and read `/var/log/nginx/error.log`. -3. **Includes not loaded**: `ssh web@example.com "grep -r 'include.*includes.d' /etc/nginx/sites-enabled/"`. -4. **Redirect loops, WebP not served, headers missing**: see the troubleshooting section of the matching guide. - -## Further reading - -- [Trellis Nginx configuration](https://roots.io/trellis/docs/nginx/) -- [Nginx documentation](https://nginx.org/en/docs/) diff --git a/docs/nginx/browser-caching/README.md b/docs/nginx/browser-caching/README.md deleted file mode 100644 index d1633de..0000000 --- a/docs/nginx/browser-caching/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# Browser Caching for Trellis - -This guide covers how to implement effective browser caching for your Trellis-managed WordPress sites. Proper caching can significantly reduce page load times, bandwidth usage, and server load. - -## Table of Contents -- [Overview](#overview) -- [Nginx Configuration for Browser Caching](#nginx-configuration-for-browser-caching) -- [Implementation](#implementation) -- [File Types and Cache Duration](#file-types-and-cache-duration) -- [Testing Cache Implementation](#testing-cache-implementation) -- [Troubleshooting](#troubleshooting) - -## Overview - -Browser caching instructs visitors' browsers to store static resources locally for a specified period, reducing the need to download the same files on subsequent visits. The included Nginx configuration adds appropriate caching headers to static files such as: - -- Images (JPEG, PNG, GIF, WebP, AVIF) -- Stylesheets (CSS) -- JavaScript files -- Fonts (WOFF, WOFF2, TTF, EOT, OTF) -- SVG files -- Other static assets - -## Nginx Configuration for Browser Caching - -The `nginx-includes/all/assets-expiry.conf.j2` file in this repository contains Nginx configuration to set appropriate caching headers for various file types. The configuration uses the `expires` directive along with `Cache-Control` headers to control how long browsers should cache content. - -### How it Works - -1. The configuration maps content types to specific expiration times -2. Different file types receive appropriate cache durations -3. HTML files are not cached to ensure fresh content -4. Admin areas and PHP files are explicitly set to not cache - -## Implementation - -1. Copy the `nginx-includes` directory to your Trellis project root (same level as `server.yml`) -2. The configuration in `nginx-includes/all/assets-expiry.conf.j2` will automatically be applied to **all sites** on your server -3. Provision your environment to apply the changes: - -```bash -# For production -trellis provision --tags nginx-includes production - -# For staging -trellis provision --tags nginx-includes staging - -# For development -trellis provision --tags nginx-includes development -``` - -**Note**: Since the file is in the `all/` directory, it will be automatically included for every site configured in Trellis. You don't need to modify your `wordpress_sites.yml` configuration. - -## File Types and Cache Duration - -The configuration sets the following cache durations: - -| File Type | Cache Duration | -|-----------|---------------| -| Images (JPEG, PNG, GIF, WebP, AVIF) | 30 days | -| CSS files | 7 days | -| JavaScript files | 7 days | -| Fonts (WOFF, WOFF2, TTF, etc.) | 30 days | -| SVG files | 30 days | -| HTML files | No cache (always fresh) | -| PHP files | No cache | -| Admin areas | No cache | -| Other static files | 1 day | - -These durations provide a good balance between performance and freshness. You can adjust them in the `assets-expiry.conf.j2` file to match your specific needs. - -## Testing Cache Implementation - -After implementation, you can verify the caching headers are working correctly: - -### Using Browser Dev Tools - -1. Open your website in Chrome or Firefox -2. Open Developer Tools (F12) -3. Navigate to the Network tab -4. Reload the page -5. Select any static resource (image, CSS, or JS file) -6. Check the Response Headers section for: - - `Cache-Control` headers - - `Expires` headers - -### Using curl - -```bash -curl -I https://example.com/wp-content/uploads/image.jpg -``` - -Look for headers like: -``` -Cache-Control: public, max-age=2592000 -Expires: Wed, 17 Jun 2025 12:00:00 GMT -``` - -## Troubleshooting - -### Resources Not Caching - -1. Verify the Nginx configuration was properly included -2. Check file permissions on the configuration file -3. Test with `curl -I` to see the actual headers being sent -4. Review Nginx error logs: `/var/log/nginx/error.log` - -### Too Aggressive Caching - -If you're developing or frequently updating assets: - -1. Reduce cache durations in the configuration -2. Use versioned file names or query strings (e.g., `style.css?v=1.2`) -3. Use a cache-busting technique in your build process - -### Cached Pages Not Updating - -For WordPress pages that should always be fresh: - -1. Verify HTML pages are not being cached (they shouldn't be with this configuration) -2. Check for additional caching layers (WordPress plugins, CDN, etc.) -3. Clear browser cache during testing - -## Advanced Configuration - -You can modify the cache durations or add more specific rules by editing the `nginx-includes/assets-expiry.conf.j2` file. For example, to change the cache duration for images: - -```nginx -# Change image caching from 30 days to 14 days -~image/ 14d; -``` - -## Performance Benefits - -Implementing proper browser caching provides several benefits: - -- Reduced bandwidth usage (up to 70% on repeat visits) -- Faster page load times for returning visitors -- Reduced server load and resource consumption -- Improved Google PageSpeed scores -- Better user experience and engagement - -For optimal performance, consider combining this caching configuration with image optimization (see the image-optimization directory) and a content delivery network (CDN). \ No newline at end of file diff --git a/docs/nginx/browser-caching/nginx-includes/all/assets-expiry.conf.j2 b/docs/nginx/browser-caching/nginx-includes/all/assets-expiry.conf.j2 index dcd98e9..1b461e5 100644 --- a/docs/nginx/browser-caching/nginx-includes/all/assets-expiry.conf.j2 +++ b/docs/nginx/browser-caching/nginx-includes/all/assets-expiry.conf.j2 @@ -1,3 +1,7 @@ +# Guide: https://wpvilla.in/nginx-browser-caching-headers-trellis-all-includes-expires/ +# Global include: nginx-includes/all/, no wordpress_sites.yml entry needed. +# Loads after 00-webp-avif.conf (alphabetical), so keep the WebP include first. + # Nginx configuration for browser caching of static assets # Settings for SVG files (moved before general static files to take precedence) diff --git a/docs/nginx/image-optimization/README.md b/docs/nginx/image-optimization/README.md deleted file mode 100644 index cd44135..0000000 --- a/docs/nginx/image-optimization/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# Image Optimization Guide - -Serve WebP and AVIF versions of your images from a Trellis-managed WordPress site. The Nginx include does the serving; you (or a plugin) supply the converted files. - -## Nginx Configuration for WebP and AVIF - -The `nginx-includes/webp-avf.conf.j2` file in this repository contains Nginx configuration to automatically serve WebP or AVIF versions of images when the browser supports them. This configuration works by checking the `Accept` header from the browser and serving the appropriate image format. - -### How it Works - -When a browser requests an image: - -1. Nginx checks if the browser accepts WebP or AVIF formats -2. If supported formats exist on the server (e.g., `image.jpg.webp` or `image.jpg.avif`), Nginx serves them instead of the original -3. If no optimized version exists, the original image is served - -### Implementation - -1. Copy the `nginx-includes` directory to your Trellis project -2. Update your Trellis WordPress site configuration to include the custom Nginx configuration: - -```yaml -# group_vars/production/wordpress_sites.yml (or staging/development) -wordpress_sites: - example.com: - # ... existing configuration ... - nginx_includes: - - nginx-includes/webp-avf.conf.j2 -``` - -3. Provision your environment to apply the changes: - -```bash -# For production -trellis provision production - -# For staging -trellis provision staging - -# For development -trellis provision development -``` - -## Generating the converted files - -The include looks for the **original filename plus the new extension**: `photo.jpg` is served as `photo.jpg.webp` or `photo.jpg.avif` when both exist next to it. - -```bash -# One image -cwebp -q 80 photo.jpg -o photo.jpg.webp -cavif --quality 80 photo.jpg - -# Everything under uploads/ -find uploads/ -name "*.jpg" -exec cwebp -q 80 {} -o {}.webp \; -``` - -Showing a WebP featured image while keeping a JPG for the share card is covered in [WordPress WebP Featured Images Without Breaking og:image](https://wpvilla.in/wordpress-webp-featured-images-og-image-original-jpg/). - -Install the tools with `brew install webp cavif` (macOS) or `apt-get install webp` (Ubuntu). Quality 75-85 suits photos; use 85-95 for images with text. Keep the originals: they are the fallback. - -A plugin that writes `.jpg.webp` files on upload works with the include too, for example [WebP Express](https://wordpress.org/plugins/webp-express/), [EWWW Image Optimizer](https://wordpress.org/plugins/ewww-image-optimizer/) or [ShortPixel](https://wordpress.org/plugins/shortpixel-image-optimiser/). - -## Verify - -```bash -curl -H "Accept: image/webp" -I https://example.com/wp-content/uploads/photo.jpg -``` - -Expect a `Content-Type: image/webp` response. If you still get the JPG, check that `photo.jpg.webp` exists with exactly that name. diff --git a/docs/nginx/image-optimization/nginx-includes/all/00-webp-avif.conf.j2 b/docs/nginx/image-optimization/nginx-includes/all/00-webp-avif.conf.j2 new file mode 100644 index 0000000..1e079b9 --- /dev/null +++ b/docs/nginx/image-optimization/nginx-includes/all/00-webp-avif.conf.j2 @@ -0,0 +1,27 @@ +# Serves photo.jpg.webp / photo.jpg.avif in place of photo.jpg when the browser +# accepts them and the file sits next to the original. +# Guide: https://wpvilla.in/serve-webp-avif-nginx-trellis-accept-header-bedrock-uploads/ +# Global include. The 00- prefix makes it load before assets-expiry.conf (includes +# are read alphabetically and the first matching regex location wins), so it sets +# its own expiry. No wordpress_sites.yml entry is needed. +# +# set/if instead of map: includes land inside the server block, where map is not allowed. + +location ~* ^/app/uploads/.+\.(jpg|jpeg|png)$ { + expires 1y; + add_header Vary Accept; + + set $avif_suffix ".none"; + set $webp_suffix ".none"; + if ($http_accept ~* "image/avif") { set $avif_suffix ".avif"; } + if ($http_accept ~* "image/webp") { set $webp_suffix ".webp"; } + + if (-f $document_root$uri$avif_suffix) { + rewrite ^ $uri$avif_suffix break; + } + if (-f $document_root$uri$webp_suffix) { + rewrite ^ $uri$webp_suffix break; + } + + try_files $uri =404; +} diff --git a/docs/nginx/image-optimization/nginx-includes/webp-avf.conf.j2 b/docs/nginx/image-optimization/nginx-includes/webp-avf.conf.j2 deleted file mode 100644 index c7880d9..0000000 --- a/docs/nginx/image-optimization/nginx-includes/webp-avf.conf.j2 +++ /dev/null @@ -1,27 +0,0 @@ -map $http_accept $webp_suffix { - default ""; - "~*image/webp" ".webp"; -} - -map $http_accept $avif_suffix { - default ""; - "~*image/avif" ".avif"; -} - -location ~* ^/app/uploads/(.*)\.(jpg|jpeg|png)$ { - root {{ www_root }}/{{ site.name }}/current/web; - - set $img_path /app/uploads/$1; - - if (-f $document_root${img_path}${avif_suffix}) { - add_header Vary Accept; - rewrite ^ $img_path$avif_suffix break; - } - - if (-f $document_root${img_path}${webp_suffix}) { - add_header Vary Accept; - rewrite ^ $img_path$webp_suffix break; - } - - try_files $uri =404; -} \ No newline at end of file diff --git a/docs/nginx/redirects/README.md b/docs/nginx/redirects/README.md deleted file mode 100644 index 2d4df29..0000000 --- a/docs/nginx/redirects/README.md +++ /dev/null @@ -1,294 +0,0 @@ -# Nginx Redirect Configuration - -Why a redirect may not deploy, and why a 301 drops the query string: [My Trellis Nginx Redirect Did Nothing](https://wpvilla.in/trellis-nginx-redirect-not-deployed-query-string-dropped/). - -Trellis nginx-includes templates for managing SEO redirects and URL rewrites. - -## Overview - -This directory contains example nginx redirect configurations that can be used in Trellis projects. Redirects are managed via nginx-includes templates that are deployed during provisioning. - -## Directory Structure - -``` -redirects/ -├── README.md # This file -└── examples/ - ├── example.com/ - │ └── seo-redirects.conf.j2 # SEO redirect examples - └── generic/ - ├── wordpress-permalinks.conf.j2 # Common WordPress redirects - └── ssl-redirects.conf.j2 # HTTP to HTTPS redirects -``` - -## How Nginx Includes Work in Trellis - -Trellis supports nginx-includes templates that are deployed to the server during provisioning. These templates are stored in your project's `trellis/nginx-includes/` directory. - -### Include Scopes - -1. **Global includes** (`nginx-includes/all/`): Apply to all sites -2. **Site-specific includes** (`nginx-includes/{site-key}/`): Apply to specific sites only - -### Directory Structure in Your Project - -``` -trellis/ -└── nginx-includes/ - ├── all/ # Global includes (all sites) - │ └── assets-expiry.conf.j2 # Browser caching rules - └── example.com/ # Site-specific includes - └── seo-redirects.conf.j2 # SEO redirects (example.com only) -``` - -### Deployment Location - -Templates are deployed to `/etc/nginx/includes.d/` on the server: - -``` -/etc/nginx/includes.d/ -├── all/ -│ └── assets-expiry.conf # Rendered from all/assets-expiry.conf.j2 -└── example.com/ - └── seo-redirects.conf # Rendered from example.com/seo-redirects.conf.j2 -``` - -## Using the Examples - -### 1. Copy Example to Your Project - -```bash -# Copy site-specific redirect template -cp ~/code/trellis-tools/redirects/examples/example.com/seo-redirects.conf.j2 \ - ~/code/yourproject.com/trellis/nginx-includes/yoursite.com/ - -# Or copy to global includes (applies to all sites) -cp ~/code/trellis-tools/redirects/examples/generic/wordpress-permalinks.conf.j2 \ - ~/code/yourproject.com/trellis/nginx-includes/all/ -``` - -### 2. Edit the Template - -Edit the `.conf.j2` file to add your site-specific redirects: - -```nginx -# SEO Redirects - Fix 404 Errors -# Date: 2025-11-30 - -# Old URL structure -> New URL structure -location = /old-page/ { - return 301 /new-page/; -} - -# Old category -> New category -location = /category/old-category/ { - return 301 /category/new-category/; -} -``` - -### 3. Deploy to Production - -```bash -cd trellis - -# Deploy nginx-includes only (fast) -trellis provision --tags nginx-includes production - -# Or deploy with full wordpress-setup -trellis provision --tags wordpress-setup production -``` - -Do not use `--tags nginx`. The includes are templated by the `wordpress-setup` -role, so that run finishes without deploying the file. - -### 4. Verify Deployment - -```bash -# Check the config file was deployed -ssh web@yoursite.com "ls -la --time-style=full-iso /etc/nginx/includes.d/yoursite.com/" -ssh web@yoursite.com "grep -n 'old-url' /etc/nginx/includes.d/yoursite.com/seo-redirects.conf" - -# Test nginx config is valid -ssh -t admin_user@yoursite.com "sudo nginx -t" - -# Test a redirect -curl -I https://yoursite.com/old-url -# Should show: HTTP/2 301 -# Location: https://yoursite.com/new-url/ -``` - -## Redirect Best Practices - -### 1. Use Permanent Redirects (301) - -```nginx -# ✅ Good - Permanent redirect (SEO-friendly) -location = /old-page/ { - return 301 /new-page/; -} - -# ⚠️ Temporary redirect (use sparingly) -location = /maintenance/ { - return 302 /temporary-page/; -} -``` - -### 2. Match Exact Paths with `=` - -```nginx -# ✅ Fast - Exact match (stops processing) -location = /old-page/ { - return 301 /new-page/; -} - -# ⚠️ Slower - Prefix match (continues processing) -location /old-page/ { - return 301 /new-page/; -} -``` - -### 3. Use Regex for Pattern Matching - -```nginx -# Redirect all portfolio items to main portfolio page -location ~ ^/portfolio-item/.*$ { - return 301 /portfolio/; -} - -# Redirect old date-based URLs to new structure -location ~ ^/(\d{4})/(\d{2})/(.*)$ { - return 301 /$3; -} -``` - -### 4. Query Strings Are Not Preserved by `return` - -```nginx -# The query string is dropped -location = /old-page/ { - return 301 /new-page/; -} -# /old-page/?utm_source=google -> /new-page/ - -# Append it to keep it -location = /old-page/ { - return 301 /new-page/$is_args$args; -} -# /old-page/?utm_source=google -> /new-page/?utm_source=google -``` - -`return` does not carry the query string over (`rewrite` does). Keep it for -redirects people reach from campaigns; drop it when the destination is another -site and the old parameters mean nothing there. - -### 5. Add Comments and Dates - -```nginx -# SEO Redirects - Fix 404 Errors -# Date: 2025-11-30 -# Reason: Content restructure after site migration - -location = /old-page/ { - return 301 /new-page/; -} -``` - - -## Testing Redirects - -### Manual Testing with curl - -```bash -# Test redirect (follow redirects with -L) -curl -I https://yoursite.com/old-url - -# Expected output: -# HTTP/2 301 -# Location: https://yoursite.com/new-url/ -``` - -### Automated Testing - -Create a test script to verify all redirects: - -```bash -#!/bin/bash -# test-redirects.sh - -SITE="https://yoursite.com" - -# Test each redirect -curl -I -s "$SITE/old-page/" | grep -q "301" && echo "✅ /old-page/ -> 301" || echo "❌ /old-page/ failed" -curl -I -s "$SITE/another-page/" | grep -q "301" && echo "✅ /another-page/ -> 301" || echo "❌ /another-page/ failed" -``` - - -## Finding URLs to redirect - -Start from your 404s: Google Search Console's coverage report, or `wp-ops 404-checker`, or the nginx access log. Compare the old and new sitemaps for removed URLs. - -## Rollback - -If redirects cause issues, you can remove them: - -```bash -# 1. Remove or comment out redirects in the .conf.j2 file - -# 2. Re-deploy -cd trellis -trellis provision --tags nginx-includes production - -# 3. Or manually remove on server -ssh -t admin_user@yoursite.com "sudo rm /etc/nginx/includes.d/yoursite.com/seo-redirects.conf && sudo nginx -t && sudo systemctl reload nginx" -``` - - -## Troubleshooting - -### Redirects Not Working - -```bash -# 1. Verify file was deployed -ssh web@yoursite.com "ls -la --time-style=full-iso /etc/nginx/includes.d/yoursite.com/" - -# 2. Check nginx config syntax -ssh -t admin_user@yoursite.com "sudo nginx -t" - -# 3. Check nginx error log -ssh -t admin_user@yoursite.com "sudo tail -f /var/log/nginx/error.log" - -# 4. Verify nginx was reloaded -ssh -t admin_user@yoursite.com "sudo systemctl status nginx" -``` - -### 404 Instead of Redirect - -- Check if redirect location path is exact match (`=`) -- Verify trailing slashes (nginx treats `/page` and `/page/` differently) -- Check if another location block takes precedence - -### Redirect Loops - -```bash -# Test for loops -curl -I -L https://yoursite.com/page/ - -# If it hangs or shows many redirects, you have a loop -``` - -Fix by checking your redirect chain: -```nginx -# ❌ Bad - Creates loop -location = /page-a/ { return 301 /page-b/; } -location = /page-b/ { return 301 /page-a/; } - -# ✅ Good - Direct to final destination -location = /page-a/ { return 301 /final-page/; } -location = /page-b/ { return 301 /final-page/; } -``` - -## Documentation - -- [Roots Trellis Nginx Includes Docs](https://roots.io/trellis/docs/nginx-includes/) -- [Nginx Location Directive](http://nginx.org/en/docs/http/ngx_http_core_module.html#location) -- [Nginx Return Directive](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return) diff --git a/docs/nginx/redirects/examples/generic/ssl-redirects.conf.j2 b/docs/nginx/redirects/examples/generic/ssl-redirects.conf.j2 index 1df8b78..aab3807 100644 --- a/docs/nginx/redirects/examples/generic/ssl-redirects.conf.j2 +++ b/docs/nginx/redirects/examples/generic/ssl-redirects.conf.j2 @@ -1,3 +1,4 @@ +# Guide: https://wpvilla.in/trellis-nginx-redirect-not-deployed-query-string-dropped/ # SSL/HTTPS Redirects # Force HTTPS for specific paths (if not using global HTTPS redirect) diff --git a/docs/nginx/redirects/examples/generic/wordpress-permalinks.conf.j2 b/docs/nginx/redirects/examples/generic/wordpress-permalinks.conf.j2 index 78852bb..8e9d584 100644 --- a/docs/nginx/redirects/examples/generic/wordpress-permalinks.conf.j2 +++ b/docs/nginx/redirects/examples/generic/wordpress-permalinks.conf.j2 @@ -1,3 +1,4 @@ +# Guide: https://wpvilla.in/trellis-nginx-redirect-not-deployed-query-string-dropped/ # WordPress Permalink Redirects # Common WordPress URL structure migrations diff --git a/docs/nginx/redirects/examples/imagewize.com/seo-redirects.conf.j2 b/docs/nginx/redirects/examples/imagewize.com/seo-redirects.conf.j2 index 869bffb..e36e009 100644 --- a/docs/nginx/redirects/examples/imagewize.com/seo-redirects.conf.j2 +++ b/docs/nginx/redirects/examples/imagewize.com/seo-redirects.conf.j2 @@ -1,3 +1,4 @@ +# Guide: https://wpvilla.in/trellis-nginx-redirect-not-deployed-query-string-dropped/ # SEO Redirects - Fix 404 Errors # Date: 2025-11-30 # Trellis Child Template - deployed via 'trellis provision --tags nginx-includes production' diff --git a/scripts/monitoring/security-monitor.sh b/scripts/monitoring/security-monitor.sh index f0cd9ed..95f7922 100755 --- a/scripts/monitoring/security-monitor.sh +++ b/scripts/monitoring/security-monitor.sh @@ -507,10 +507,10 @@ generate_block_recommendations() { echo "" echo -e "${CYAN}To block in Trellis:${NC}" - echo "1. Create roles/wordpress-setup/templates/deny-ips.conf.j2" + echo "1. Create trellis/nginx-includes/all/deny-ips.conf.j2" echo "2. Add the deny statements above" - echo "3. Add to wordpress_sites YAML: nginx_includes: deny-ips.conf" - echo "4. Run: trellis provision production" + echo "3. Run: trellis provision --tags nginx-includes production" + echo " Guide: https://wpvilla.in/nginx-deny-list-trellis-cidr-whitelist-self-lockout/" } # ============================================================================