# Deployment Examples & Verification

This document contains detailed NGINX configuration examples, verification steps, and common deployment notes. For a quick overview, see the [README](../../README.md).

## Table of Contents

1. [NGINX Configuration Examples](#nginx-configuration-examples)
2. [Quick Verification (curl)](#quick-verification-curl)
3. [Common Deployment Notes](#common-deployment-notes)
4. [Common Issues Quick Reference](#common-issues-quick-reference)

---

## NGINX Configuration Examples

### Minimal Enablement (Reverse Proxy / HTTP Upstream)

```nginx
load_module modules/ngx_http_markdown_filter_module.so;

http {
    # Safer default: enable only where you want it first
    markdown_filter off;

    server {
        listen 8080;

        location /docs/ {
            markdown_filter on;
            # Optional for a first rollout: ask upstream for uncompressed HTML
            proxy_set_header Accept-Encoding "";
            proxy_pass http://backend;
        }
    }
}
```

### Minimal Enablement (PHP-FPM)

If your site is served by `php-fpm` (common setup), use `fastcgi_pass` instead of `proxy_pass`:

```nginx
load_module modules/ngx_http_markdown_filter_module.so;

http {
    markdown_filter off;
    markdown_on_error pass;

    server {
        listen 8080;
        root /var/www/html;
        index index.php index.html;

        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }

        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass 127.0.0.1:9000;  # or unix:/run/php/php-fpm.sock

            # Enable conversion for HTML generated by PHP
            markdown_filter on;
        }
    }
}
```

### Global Enablement + Path Exceptions

Many existing sites want the opposite behavior: enable Markdown conversion globally, then disable it for API/static/download paths.

Reverse proxy example:

```nginx
load_module modules/ngx_http_markdown_filter_module.so;

http {
    markdown_filter on;
    markdown_on_error pass;

    server {
        listen 8080;

        location / {
            proxy_set_header Accept-Encoding "";
            proxy_pass http://backend;
        }

        # Common exclusions
        location /api/ { markdown_filter off; proxy_pass http://backend; }
        location /assets/ { markdown_filter off; proxy_pass http://backend; }
        location /downloads/ { markdown_filter off; proxy_pass http://backend; }
    }
}
```

PHP-FPM example (global on, exceptions by path):

```nginx
load_module modules/ngx_http_markdown_filter_module.so;

http {
    markdown_filter on;
    markdown_on_error pass;

    server {
        listen 8080;
        root /var/www/html;
        index index.php index.html;

        location ^~ /api/ { markdown_filter off; try_files $uri $uri/ /index.php?$query_string; }
        location ^~ /assets/ { markdown_filter off; }
        location ^~ /uploads/ { markdown_filter off; }

        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }

        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass 127.0.0.1:9000;
        }
    }
}
```

Notes for the global-on pattern:

- Start with `markdown_on_error pass;` (fail-open) to reduce rollout risk
- Exclude API/static/media/download routes explicitly for clarity
- Existing `.md` files usually do not need conversion (module only converts eligible `text/html` responses)
- Keep using `Accept: text/markdown` for verification; browsers with normal `Accept` headers still get HTML
- In PHP setups, verify the final request lands in the location block you expect

### Production-Oriented Example (gzip + php-fpm + static cache + API exceptions)

This example reflects a common production PHP-FPM deployment: global enablement, explicit exclusions, static asset caching, and gzip enabled (including Markdown responses).

```nginx
load_module modules/ngx_http_markdown_filter_module.so;

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile on;

    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types
        text/plain
        text/css
        text/xml
        application/json
        application/javascript
        application/xml
        image/svg+xml
        text/markdown;

    markdown_filter on;
    markdown_on_error pass;
    markdown_max_size 10m;
    markdown_timeout 5s;

    server {
        listen 80;
        server_name example.com;
        root /var/www/example/public;
        index index.php index.html;

        location ^~ /api/ {
            markdown_filter off;
            try_files $uri $uri/ /index.php?$query_string;
        }

        location ~* \.md$ {
            markdown_filter off;
            try_files $uri =404;
        }

        location ~* \.(?:css|js|mjs|map|jpg|jpeg|png|gif|svg|ico|webp|woff2?|ttf)$ {
            markdown_filter off;
            expires 30d;
            add_header Cache-Control "public, max-age=2592000, immutable";
            access_log off;
            try_files $uri =404;
        }

        location ^~ /uploads/ {
            markdown_filter off;
            expires 7d;
            add_header Cache-Control "public, max-age=604800";
            try_files $uri =404;
        }

        location ^~ /downloads/ {
            markdown_filter off;
            try_files $uri =404;
        }

        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }

        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass unix:/run/php/php-fpm.sock;
        }
    }
}
```

If you are using a reverse proxy upstream instead of PHP-FPM, `proxy_set_header Accept-Encoding "";` is still a useful first-step simplification, but it is optional. The module can automatically decompress upstream `gzip`, `br`, and `deflate` responses when you keep compression enabled end to end.

### Bot-Targeted Conversion (User-Agent Based)

AI crawlers and agent bots typically do not send `Accept: text/markdown`. They use standard browser-like Accept headers when fetching pages. If you want specific bots to receive Markdown automatically, you can rewrite the Accept header at the NGINX layer based on User-Agent matching.

This is a practical pattern because it requires no application or module code changes — the module's existing content negotiation handles the conversion once it sees `text/markdown` in the Accept header. Operators can manage the bot list entirely through NGINX configuration.

```nginx
load_module modules/ngx_http_markdown_filter_module.so;

http {
    # Rewrite Accept header for known AI bots
    map $http_user_agent $bot_accept_override {
        default         "";
        "~*ClaudeBot"   "text/markdown, text/html;q=0.9";
        "~*GPTBot"      "text/markdown, text/html;q=0.9";
        "~*Googlebot"   "text/markdown, text/html;q=0.9";
    }

    # Use the override when present, otherwise keep the original Accept
    map $bot_accept_override $final_accept {
        ""      $http_accept;
        default $bot_accept_override;
    }

    server {
        listen 8080;

        location / {
            markdown_filter on;
            markdown_on_error pass;
            proxy_set_header Accept $final_accept;
            proxy_pass http://backend;
        }

        # API and static paths still excluded
        location /api/ { markdown_filter off; proxy_pass http://backend; }
        location /assets/ { markdown_filter off; proxy_pass http://backend; }
    }
}
```

Verification:

```bash
# Simulate ClaudeBot — should return Markdown
curl -sD - -o /dev/null -A "ClaudeBot/1.0" http://localhost:8080/
# Expected: Content-Type: text/markdown; charset=utf-8

# Normal browser request — should return HTML
curl -sD - -o /dev/null -H "Accept: text/html" http://localhost:8080/
# Expected: Content-Type: text/html
```

For a ready-to-use template, see [examples/nginx-configs/06-bot-targeted-conversion.conf](../../examples/nginx-configs/06-bot-targeted-conversion.conf).

For the content negotiation details behind this pattern, see [Content Negotiation: Bot-Targeted Conversion](../features/CONTENT_NEGOTIATION.md#bot-targeted-conversion-user-agent-based).

### Ready-to-Use Configuration Templates

See [examples/nginx-configs/](../../examples/nginx-configs/) for copy-paste-ready templates:

- [Minimal Reverse Proxy](../../examples/nginx-configs/01-minimal-reverse-proxy.conf)
- [Minimal PHP-FPM](../../examples/nginx-configs/02-minimal-php-fpm.conf)
- [Global with Exceptions](../../examples/nginx-configs/03-global-with-exceptions.conf)
- [Production Full](../../examples/nginx-configs/04-production-full.conf)
- [High Performance](../../examples/nginx-configs/05-high-performance.conf)

---

## Quick Verification (curl)

After enabling the module and reloading NGINX:

### 1. Check Module Loading

```bash
sudo tail -20 /usr/local/nginx/logs/error.log | grep markdown
# Expected: "markdown filter: converter initialized"
```

### 2. Verify Markdown Conversion

```bash
curl -sD - -o /dev/null -H "Accept: text/markdown" http://localhost:8080/
# Expected headers:
# Content-Type: text/markdown; charset=utf-8
# Vary: Accept
```

### 3. Confirm HTML Still Works

```bash
curl -sD - -o /dev/null -H "Accept: text/html" http://localhost:8080/
# Expected: Content-Type: text/html
```

### 4. Inspect Converted Content

```bash
curl -s -H "Accept: text/markdown" http://localhost:8080/ | head -20
# Should see Markdown (# headings, [links](url)), not HTML tags
```

### 5. Verify Included vs Excluded Paths

```bash
# Included route (should convert)
curl -sD - -o /dev/null -H "Accept: text/markdown" http://localhost:8080/docs/ | grep -i content-type

# Excluded route (should stay original type)
curl -sD - -o /dev/null -H "Accept: text/markdown" http://localhost:8080/api/health | grep -i content-type
```

Note: Prefer GET-based header checks (`curl -sD - -o /dev/null ...`) over `curl -I` for verification. In proxied deployments, `HEAD` requests may not carry an upstream body, which can cause fail-open behavior.

### If It Does Not Convert

Common causes:

- `markdown_filter on;` is not enabled in the active `http/server/location`
- Request did not include `Accept: text/markdown`
- Upstream response is not eligible (not `200`, not `text/html`, or exceeds size limits)

For more diagnostics, see [INSTALLATION.md](INSTALLATION.md) and [OPERATIONS.md](OPERATIONS.md).

---

## Common Deployment Notes

### gzip in NGINX is usually fine

- The module converts HTML to Markdown first, then NGINX `gzip`/`brotli` compresses the final response
- Add `text/markdown` to `gzip_types` if your config uses an explicit list

### Upstream compression is automatically handled

The module automatically detects and decompresses upstream compressed content (`gzip`, `br`, `deflate`). No special configuration needed.

Optional optimization — conditionally disable upstream compression for Markdown requests:

```nginx
map $http_accept $markdown_requested {
    default 0;
    "~*(^|,)\\s*text/markdown(\\s*;|,|$)" 1;
}

map $markdown_requested $upstream_accept_encoding {
    0 $http_accept_encoding;
    1 "";
}

location / {
    proxy_set_header Accept-Encoding $upstream_accept_encoding;
    proxy_pass http://backend;
}
```

### PHP (`php-fpm`) deployments

- PHP pages are commonly served from `location ~ \.php$` via `fastcgi_pass`
- If you only enable `markdown_filter on;` in `location /`, PHP requests may end up in another location block and not be converted
- Prefer enabling in the PHP location (or `server` scope)

### PHP application-level compression can interfere

If PHP compresses output itself (`zlib.output_compression=On`), disable that for routes you want to convert, and let NGINX handle response compression instead.

### Existing `.md` files bypass conversion automatically

The module only converts eligible `text/html` responses. Native Markdown or plain text responses are bypassed. You can still explicitly exclude `.md` paths:

```nginx
location ~* \.md$ {
    markdown_filter off;
}
```

### Recommended rollout strategy

1. Enable only a docs/help/test route
2. Verify `Content-Type: text/markdown` and body format
3. Check normal browser HTML path still works
4. Expand to more locations
5. Keep API/static/media routes disabled

---

## Common Issues Quick Reference

| Issue | Likely Cause | Quick Fix |
|-------|--------------|-----------|
| Returns HTML instead of Markdown | Module not enabled or Accept header missing | [Details](OPERATIONS.md#issue-1-conversion-not-occurring) |
| High failure rate | Resource limits or malformed HTML | [Details](OPERATIONS.md#issue-2-high-failure-rate) |
| Slow response times | Conversion timeout or high system load | [Details](OPERATIONS.md#issue-3-slow-conversion-performance) |
| Module won't load | Missing dependencies or version mismatch | [Details](INSTALLATION.md#troubleshooting) |
| Cache returns wrong content | Cache key misconfiguration | [Details](OPERATIONS.md#issue-5-incorrect-cache-behavior) |

Complete troubleshooting guide: [OPERATIONS.md](OPERATIONS.md#troubleshooting)


## Document Updates

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 0.5.0 | 2026-04-21 | docs-standardization | Standardized formatting, added mermaid diagrams where applicable, verified directive accuracy against code, added update tracking section |
| 0.6.2 | 2026-05-08 | Kang | Unified version narrative to 0.6.2 current release line |
