> ## Documentation Index
> Fetch the complete documentation index at: https://docs.websitestudio.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Nginx Configuration for VoxelSite

> Configure Nginx for VoxelSite with clean URL routing, Agent API support, security blocks, and a complete Laravel Forge config example.

VoxelSite ships with an `.htaccess` file that handles clean URL routing and security rules on Apache. **Nginx ignores `.htaccess` entirely** — you need to add equivalent rules to your Nginx site configuration.

Without this configuration, visiting `/about` will show your homepage instead of `about.php`, because Nginx doesn't know to try the `.php` extension.

## The essential fix: clean URLs

VoxelSite generates pages as PHP files (`about.php`, `contact.php`, etc.) and links to them with clean URLs (`/about`, `/contact`). On Apache, the `.htaccess` rewrite rule handles this automatically. On Nginx, you need to add a **named location** that rewrites extensionless URLs to `.php`.

Find the `location /` block in your Nginx config and replace it with:

```nginx theme={null}
# Clean URLs: /about → /about.php (processed by PHP)
location / {
    try_files $uri $uri/ @cleanurls;
}

location @cleanurls {
    rewrite ^/(.+)$ /$1.php last;
}
```

<Note>
  **Why not `$uri.php` in `try_files`?** Adding `$uri.php` as a middle argument in `try_files` finds the file but serves it as raw text — it bypasses the PHP handler. The named `@cleanurls` location with `rewrite ... last` does a proper internal redirect that re-evaluates against `location ~ \.php$`, ensuring PHP-FPM processes the file.
</Note>

## Agent API routing

<Note>
  **The Agent API works without this rule.** The Studio router automatically detects `/agent/v1/` requests and forwards them to the Agent API router. The Nginx rewrite below is a performance optimization — it routes requests directly to the Agent API router, skipping the Studio router overhead. For most installations, the automatic fallback is sufficient.
</Note>

If you want direct routing (recommended for production), add this **before** the `location /` block:

```nginx theme={null}
# Agent API: route all /_studio/api/agent/v1/* to the router
# The negative lookahead (?!.*\.php) prevents a rewrite loop —
# without it, the rewritten router.php URI matches this block again.
location ~ ^/_studio/api/agent/v1/(?!.*\.php)(.*)$ {
    rewrite ^/_studio/api/agent/v1/(.*)$ /_studio/api/agent/v1/router.php?_path=$1&$args last;
}
```

This mirrors the Apache `.htaccess` rule, which passes the path as `?_path=$1`. For example, a request to `/_studio/api/agent/v1/pages` will be rewritten to `/_studio/api/agent/v1/router.php?_path=pages`.

## Recommended security blocks

The `.htaccess` also blocks access to sensitive directories and files. Add these **before** the `location /` block:

```nginx theme={null}
# Block sensitive directories
location ~ ^/(\.ai|\.git|_data|scripts|vendor|node_modules|docs)/ {
    deny all;
    return 403;
}

# Block sensitive root files
location ~ ^/(composer\.(json|lock)|package(-lock)?\.json)$ {
    deny all;
    return 403;
}

# Block form definition files (contain spam protection config)
location ~ ^/assets/forms/.*\.json$ {
    deny all;
    return 403;
}

# Block sensitive file types
location ~ \.(db|sqlite|sqlite3|sql|sh|env|bak|log)$ {
    deny all;
    return 403;
}
```

## Complete Nginx config example (Forge)

Here's a complete site configuration block for Laravel Forge. Replace the site ID (`3050636`) with your own:

```nginx theme={null}
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_dhparam /etc/nginx/dhparams.pem;

add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";

index index.html index.htm index.php;

charset utf-8;

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/YOUR_SITE_ID/server/*;

# ── Security blocks ──

location ~ ^/(\.ai|\.git|_data|scripts|vendor|node_modules|docs)/ {
    deny all;
    return 403;
}

location ~ ^/(composer\.(json|lock)|package(-lock)?\.json)$ {
    deny all;
    return 403;
}

location ~ ^/assets/forms/.*\.json$ {
    deny all;
    return 403;
}

location ~ \.(db|sqlite|sqlite3|sql|sh|env|bak|log)$ {
    deny all;
    return 403;
}

# ── Agent API routing ──

location ~ ^/_studio/api/agent/v1/(?!.*\.php)(.*)$ {
    rewrite ^/_studio/api/agent/v1/(.*)$ /_studio/api/agent/v1/router.php?_path=$1&$args last;
}

# ── Clean URL routing ──

location / {
    try_files $uri $uri/ @cleanurls;
}

location @cleanurls {
    rewrite ^/(.+)$ /$1.php last;
}

location = /favicon.ico { access_log off; log_not_found off; }
location = /favicon.svg { access_log off; log_not_found off; }
location = /robots.txt  { access_log off; log_not_found off; }

access_log off;
error_log  /var/log/nginx/YOUR_SITE_ID-error.log error;

error_page 404 /index.php;

location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    include forge_fastcgi_defaults;

    # Forward the Authorization header to PHP (required for Agent API Bearer tokens)
    fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}

location ~ /\.(?!well-known).* {
    deny all;
}
```

<Warning>
  The `fastcgi_param HTTP_AUTHORIZATION $http_authorization;` line is essential if you use the Agent API. Without it, Nginx strips the `Authorization` header before it reaches PHP, causing all authenticated API calls to fail with 401.
</Warning>

## Where to edit this on Forge

<Steps>
  <Step title="Open the Nginx configuration editor">
    Go to **Sites → your site → Nginx Configuration** (the **Edit** button on the files panel).
  </Step>

  <Step title="Locate the server block">
    Find the `server { ... }` block in your config.
  </Step>

  <Step title="Apply the changes">
    Replace the `location /` block and add the security rules as shown above.
  </Step>

  <Step title="Save">
    Click **Save** — Forge automatically reloads Nginx.
  </Step>
</Steps>
