# Nginx

Two-layer reverse proxy: TLS on the host and per-domain server blocks inside the VPS, with testing and automatic rollback.

Source: https://ardumaker.com/en/docs/nginx/

- Category: Servers and infrastructure
- Status: Available
- Requirements: A company VPS, running and with a host assigned; Being a member of the company with access to that VPS; A domain pointing to the VPS: a subdomain of the ArduMaker zone or your own domain; A VPS operating system that supports the “VPS nginx” feature (check it with `ver_funciones_vps`)
- MCP tools (Read only): `verificar_dominio_vps`, `listar_dominios_vps`, `ver_nginx_dominio_vps`, `ver_logs_nginx_vps`
- MCP tools (Write or modify): `crear_dominio_vps`, `activar_dominio_vps`
- MCP tools (Destructive): `editar_nginx_dominio_vps`, `eliminar_dominio_vps`

## What can you do with it?

- Publish a domain to a VPS: the shared host nginx terminates HTTPS and forwards traffic to the nginx running inside the VPS.
- Read and rewrite a domain's server block inside the VPS, with an `nginx -t` test and automatic rollback if anything fails.
- List the domains that reach the VPS, with their `proxy_pass`, the port forward they use and the deploy they are linked to.
- Read each domain's access and error logs (the last 500 lines).
- Delete a domain: its host server block, its VPS server block and, if it belongs to the ArduMaker zone, its DNS record are removed.

## When should you use it?

- Publishing your own application (Node, Python, Go…) that listens on a local port of the VPS.
- Writing the server block when no Git deploy or ArduMaker app wrote it, that is, when the domain answers 502 or the default site.
- Tuning a site's proxy: static folders, WebSockets, special routes.
- Diagnosing a 502, a broken redirect or a domain that does not load.

## How does it work in ArduMaker?

A domain goes through **two nginx layers**. The shared host nginx terminates TLS on port 443 and does `proxy_pass` to a host port (`host_port`) that forwards to port 443 of the VPS. Inside the VPS, nginx listens on 443 **in plain HTTP** (no `ssl`) and routes by `server_name` to each service.

### Activating a domain (host side)

1. The domain is checked to be a strict FQDN (letters, digits, hyphens and dots, at least two labels). If it is not, the answer is `dominio_invalido` before anything is touched.
2. A domain that already belongs to another company is rejected (`dominio_ocupado`).
3. Without `host_port`, the target is the VPS nginx: if the forward to port 443 is missing, it is created closed to the internet. With `host_port`, it must be the host port of a forward of THIS VPS.
4. If the name is not a single-level subdomain of the ArduMaker zone, the DNS is checked to reach the host and, with `create_cert`, the certificate is issued (see [SSL](https://ardumaker.com/en/docs/ssl)).
5. The host server block is written. With `use_ssl` (the default) port 80 redirects to HTTPS with a 301 and 443 serves HTTPS with HTTP/2. `nginx -t` runs and nginx reloads; if the test fails, what was just written is removed.
6. If the host already has a server block with that name pointing to another VPS, it is left untouched and the operation fails with `ruta_ocupada`.

### Writing the server block inside the VPS

1. Edits to the nginx of one VPS run one at a time: two deploys, or an app and a manual edit, wait in line.
2. If `/etc/nginx/sites-available/<dominio>.conf` already exists, a `.backup` copy is saved.
3. The full content is written (it replaces the file, it does not merge) and the link in `/etc/nginx/sites-enabled/` is created.
4. `nginx -t` runs. If it fails, the file and link are removed, the copy is restored and the previous link is recreated: the answer is `Nginx configuration test failed - changes reverted`.
5. nginx is restarted (`systemctl restart nginx`). If it does not start, the same rollback happens and the answer is `Nginx reload failed - changes reverted`.
6. If everything went well, the `.backup` copy is deleted.

The content has no validation other than `nginx -t`: it is the same power as the panel's configuration editor.

> **INFO:** Activating a domain **does not write** the VPS server block. It is written by the deploy of a Git app that has that domain, or by the editor (`editar_nginx_dominio_vps`). Until then the domain answers with the VPS default site or with a 502.

## What actually changes?

### On the shared host

- One server block per domain, not visible from the VPS.
- If it did not exist, a port forward to port 443 of the VPS. It is not opened in the firewall and stays closed to the other VPS on the host.

### Inside the VPS

- `/etc/nginx/sites-available/<dominio>.conf` and its link `/etc/nginx/sites-enabled/<dominio>.conf`.
- The logs `/var/log/nginx/<dominio>_access.log` and `/var/log/nginx/<dominio>_error.log`, if the server block declares them (ArduMaker templates do).
- Deleting the domain removes the file and the link, and the domain disappears from the deploys that had it linked.

Each operation is written to the VPS activity log under the person who did it (or who authorized the agent).

## Ports and proxy

| Hop | Port | Protocol | Notes |
| --- | --- | --- | --- |
| Internet → host | 80 / 443 | HTTP / HTTPS (TLS 1.2 and 1.3, HTTP/2) | With `use_ssl`, port 80 answers 301 to HTTPS |
| Host → VPS | `host_port` | Plain HTTP | Port forward assigned automatically; not public |
| VPS nginx | 443 | Plain HTTP | `listen 443;` without `ssl`; routes by `server_name` |
| VPS nginx → service | service port | HTTP | Set by `proxy_pass` (usually `127.0.0.1:<port>`) |

### What the host proxy adds

- Headers `Host`, `X-Real-IP` (the client IP, always overwritten), `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host` and `X-Forwarded-Port`.
- WebSocket support (`Upgrade` and `Connection`).
- `client_max_body_size 100M` and connect, send and read timeouts of 360 seconds.

The VPS nginx takes the client IP from `X-Real-IP` only when the connection arrives over loopback, which is how the host proxy arrives. An app behind it sees the real IP in `X-Real-IP`.

## Server block example

A minimal server block for an app listening on port 3000 of the VPS:

```nginx
server {
    listen 443;
    listen [::]:443;
    server_name app.ejemplo.com;

    access_log /var/log/nginx/app.ejemplo.com_access.log;
    error_log /var/log/nginx/app.ejemplo.com_error.log;

    absolute_redirect off;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
```

- No `ssl` and no certificates: TLS ends at the host and certificates are not copied to the VPS.
- `absolute_redirect off;` makes nginx's own redirects (for example `/blog` → `/blog/`) relative. Without it they come out as `http://dominio:443/blog/` and the browser cannot reach them.
- Logs named `<dominio>_access.log` and `<dominio>_error.log` are the ones `ver_logs_nginx_vps` reads.

## Conflicts and concurrency

- `dominio_ocupado` (409): the domain belongs to another company (it falls in a zone another company registered, another one published it with Apps, or a deploy of another one has it linked).
- `ruta_ocupada`: the host already has a server block with that name pointing to another VPS. It is left untouched.
- `host_port_no_es_del_vps` (400): the `host_port` sent is not a forward of this VPS. The answer carries `valid_host_ports` and `vps_nginx_host_port`. `host_port` is **not** the port the service listens on inside the VPS.
- Two simultaneous edits to the nginx of the same VPS do not overwrite each other: they run one after the other.
- A domain already used by an app or by n8n shows as used in `listar_dominios_vps` (fields `used_by`, `linked_to_repo` and `puerto_in_vps`).

## Diagram

**How a request reaches your service**

```text
Browser (requests https://app.ejemplo.com)
  ↓
Cloudflare (only subdomains of the ArduMaker zone, proxied)
  ↓
Host nginx :443 (TLS and certificate; proxy_pass to host_port)
  ↓
Port forward (host_port → VPS port 443, closed to the internet)
  ↓
VPS nginx :443 (plain HTTP; picks the server block by server_name)
  ↓
Service (127.0.0.1:<port> inside the VPS)
```

With your own domain and no proxy, the request reaches the host directly. If a `host_port` of a forward to another port is chosen when activating, traffic skips the VPS nginx (`via: direct`).

## Expected result

- `activar_dominio_vps` returns `nginx` (`success`, `domain`, `host_port`, `exit_code`), `certificate` and `target` (`host_port`, `vps_port`, `via`, `next_step`).
- `via: "vps_nginx"`: the domain reaches the VPS nginx and needs its server block. `via: "direct"`: it goes straight to that VPS port, which must answer plain HTTP.
- `listar_dominios_vps` shows each domain with `server_name`, `proxy_pass`, `puerto`, `puerto_in_vps` and `linked_to_repo`.

To check it from outside:

```bash
curl -sI https://app.ejemplo.com/
# una carpeta debe redirigir de forma relativa: Location: /blog/
curl -sI https://app.ejemplo.com/blog
```

## Errors and troubleshooting

| Symptom | Cause | What to do |
| --- | --- | --- |
| 502 or the default site after activating | There is no server block in the VPS, or the service does not listen on the `proxy_pass` port | Write the server block; check the domain error log and the service status |
| `Nginx configuration test failed - changes reverted` | `nginx -t` rejected the configuration; the previous version is back | Fix the content (it comes in `nginx_test_error`) and send it again |
| `Nginx reload failed - changes reverted` | nginx could not restart, for example because a port is taken | Read `ver_logs_systemd_vps` for the `nginx` service |
| `nginx_invalido` | The host nginx rejected the domain configuration; nothing was published. The details stay in the server log | Retry; if it happens again, report it |
| `certificado_del_host_ausente` | The host has no certificate for that domain | Activate with `create_cert=true`, or with `use_ssl=false` |
| `redireccion_fallida` | The host could not open the port towards the VPS nginx | Retry |
| `Domain is not pointing to the host IP` / `Domain could not be resolved` | The DNS does not point to the VPS yet | Check with `verificar_dominio_vps` and wait for propagation |
| A folder redirects to `http://dominio:443/...` | The server block builds absolute redirects | Add `absolute_redirect off;` |
| `Access log file not found for domain` | The server block does not declare a log with the expected name | Declare `access_log` and `error_log` as in the example |
| `Config file not found or unreadable` | That domain has no server block in the VPS | Create it with `editar_nginx_dominio_vps` |

If the host or the VPS do not answer, the result is a state error (424) that can be retried. `activar_dominio_vps` and `editar_nginx_dominio_vps` are safe to repeat: the first one asks before creating each piece and the second one replaces the whole file. `eliminar_dominio_vps` needs care: it deletes the ArduMaker zone DNS record and both server blocks.

## MCP access

Yes, there is MCP access. The tools are in the `/mcp` and `/mcp/vps` profiles and act with the permissions of the person who authorized the agent; a read-only agent only sees the read tools.

| Tool | Type | What it does |
| --- | --- | --- |
| `verificar_dominio_vps` | Read | Checks whether the DNS already reaches the VPS host |
| `listar_dominios_vps` | Read | VPS domains and their target |
| `ver_nginx_dominio_vps` | Read | Reads the VPS server block |
| `ver_logs_nginx_vps` | Read | Access or error logs; they come marked as untrusted content |
| `crear_dominio_vps` | Write | ArduMaker zone DNS record (accepts `idempotency_key`) |
| `activar_dominio_vps` | Write | Publishes on the host; returns an `operacion_id` for `ver_operacion` |
| `editar_nginx_dominio_vps` | Destructive | Replaces the whole server block and restarts nginx |
| `eliminar_dominio_vps` | Destructive | Deletes the zone DNS and both server blocks |

Typical sequence: `crear_dominio_vps` (only for the ArduMaker zone) → `verificar_dominio_vps` → `activar_dominio_vps` without `host_port` → `editar_nginx_dominio_vps` if no deploy writes the server block. Reference for each tool at [/mcp/docs](https://ardumaker.com/en/mcp/docs) and examples at [/mcp/dominios](https://ardumaker.com/en/mcp/dominios).

## Related features

- [SSL certificates](https://ardumaker.com/en/docs/ssl/)
- [Domains and DNS](https://ardumaker.com/en/docs/dominios-y-dns/)
- [Firewall and ports](https://ardumaker.com/en/docs/firewall-y-puertos/)
- [GitHub deploy](https://ardumaker.com/en/docs/github-deploy/)
- [VPS servers](https://ardumaker.com/en/docs/vps/)
- [Monitoring](https://ardumaker.com/en/docs/monitorizacion/)
