Série Nginx #3: Conhecendo as diretivas
Tags: braziliandevs, nginx, devops, webdev
Published: 2021-09-08 14:39:00

## Diretivas do Nginx

Olá, galerinha, pronto para mais uma postagem? 🥳

Vamos conhecer e aprender como utilizar algumas das diretivas mais importantes do Nginx.

De acordo com a documentação oficial, o nginx consiste em módulos controlados por diretivas especificadas nos arquivos de configuração. Elas são divididas em *diretivas simples* e *diretivas em bloco*.

Uma diretiva simples consiste no nome e nos parâmetros separados por espaços e com um ponto e vírgula (`;`) no final.

Uma diretiva de bloco permite que o desenvolvedor utilize outras diretivas dentro de chaves (`{` e `}`). Quando isso ocorre, essa diretiva de bloco é chamada de contexto (exemplos: `events`, `http`, `server` e `location`).

Vamos conhecer as diretivas.

-------------

### add_header ([fonte](https://github.com/nginx/nginx/blob/67d160bf25e02ba6679bb6c3b9cbdfeb29b759de/src/http/modules/ngx_http_headers_filter_module.c#L543-L560))

Como o nome deixa claro, essa diretiva serve para definir *headers* direto do Nginx. Isso é útil para definir os cabeçalhos de CORS, por exemplo.

```conf
# Syntax: add_header name value [always];
# Default: —
# Context: http, server, location, if in location
#
location /upload {
    add_header "Access-Control-Allow-Origin" "https://*.valdeir.dev";
    add_header "Access-Control-Allow-Methods" "GET, OPTIONS";
    add_header X-Dev "Valdeir Psr" always;
}
```

O cabeçalho da requisição acima será:

```bash
http http://php.valdeirpsr.com.br/upload/foto.jpg
HTTP/1.1 200 OK
Content-Length: 1333425
Content-Type: image/jpeg
Date: Fri, 27 Aug 2021 15:28:23 GMT
access-control-allow-methods: GET, OPTIONS
access-control-allow-origin: https://*.valdeir.dev
x-dev: Valdeir Psr

+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+
```

-------------

### alias ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/ngx_http_core_module.c#L1875-L1945))

Com a diretiva `alias`, podemos substituir a localização da pasta definida na diretiva `root`.

```conf
root /var/www/html;

location /css {
    # Syntax: alias path;
    # Default: —
    # Context: location
    #
    alias /dev/vda2/css/;
}
```

Isso significa que a requisição `https://valdeir.psr/css/global.css` carregará os arquivos da pasta `/dev/vda2/css`.

| HTTP                                | File                     |
| ----------------------------------- | ------------------------ |
| https://valdeir.psr/css/global.css  | /dev/vda2/css/global.css |
| https://valdeir.psr/images/logo.svg | /var/www/html/images/logo.svg |

-------------

## client_max_body_size ([fonte](https://github.com/nginx/nginx/blob/67d160bf25e02ba6679bb6c3b9cbdfeb29b759de/src/http/ngx_http_request_body.c#L1114-L1127))

Você pode usar essa diretiva para configurar o tamanho máximo do corpo da requisição. Útil para limitar o tamanho de arquivos em formulários de *upload*. Caso o valor definido seja `0`, desabilita a verificação do tamanho dos dados enviados.

```conf
# Syntax: client_max_body_size size;
# Default: client_max_body_size 1m;
# Context: http, server, location
#
client_max_body_size 1G;
```

-------------

## error_page ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/ngx_http_core_module.c#L4674-L4789))

A diretiva permite a customização de uma página de erro. Quando a página retornar o erro 404, por exemplo, então o Nginx irá retornar para o usuário o arquivo `/not_found.html`.

```conf
# Syntax: error_page code ... [=[response]] uri;
# Default: —
# Context: http, server, location
#
error_page 404             /not_found.html
error_page 500 502 503 504 /50x.html;
```

Também é possível redirecionar o usuário para outra página ou domínio com um novo *status code*

```
server_name valdeir.dev;

error_page 401 =200 https://auth.$server_name;
```

> **Observação**: O parâmetro `code` não pode ser igual a *499*, menor que *300* ou maior que *599*.

-------------

## listen ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/ngx_http_core_module.c#L3868-L4211))

Essa diretiva permite definir qual o endereço e/ou porta o Nginx deverá "ouvir" as requisições. Por padrão, o servidor usará as portas 80 e 8000.

```conf
# Syntax address: listen address[:port] [default_server] [ssl] [http2];
#
# Syntax port: listen port [default_server] [ssl] [http2];
#
# Syntax PID: listen unix:path [default_server] [ssl] [http2];
#
# Default: listen *:80 | *:8000;
# Context: server
#
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
```

**Parâmetros adicionais**

<dl>
  <dt><b>default_server</b></dt>
  <dd>Informa se o servidor virtual é o padrão. Caso o parâmetro não seja informado, o primeiro servidor localizado será definido como padrão. Você pode utilizar esse parâmetro apenas em um servidor virtual.</dd>

  <dt><b>ssl</b></dt>
  <dd>Esse parâmetro informa se o servidor deve receber conexões criptografadas. Lembrando que, ao utilizar esse parâmetro, é importante configurar as diretivas <code>ssl_certificate</code> e <code>ssl_certificate_key</code> com as chaves para que o Nginx consiga descritografar as informações e repassá-las à aplicação.</dd>

  <dt><b>http2</b></dt>
  <dd>Indica que o Nginx deve aceitar conexões com o protocolo <a href="https://datatracker.ietf.org/doc/html/rfc7540">HTTP/2</a>. Lembrando que o SSL é obrigatório para o uso desse protocolo.</dd>
</dl>

-------------

## location ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/ngx_http_core_module.c#L2950-L3140))

A diretiva mais utilizada. Ela serve para aplicar configurações de acordo com a URI da requisição, que pode ser identificada com o uso de expressões regulares ou correspondência exata.

```conf
# Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }
#         location @name { ... }
# Default: —
# Context: server, location
#
```

**Utilizando os modificadores**

O uso do modificador `=` indica que as configurações deverão ser aplicadas **apenas** quando a *URI* for exatamente igual ao valor informado no parâmetro `URI`. No exemplo abaixo, as configurações serão aplicadas na URI `/upload`, porém **não** serão aplicadas na URI `/upload/avatar.svg`, por exemplo.

```conf
location = /upload {
    ...
}
```

O uso do modificador `~` indica que as configurações deverão ser aplicadas quando a URI for correspondente à expressão regular definida na diretiva. No exemplo abaixo, as configurações serão aplicadas nas URI `/upload/avatar.jpeg`, `/upload/avatar.jpg`, `/upload/avatar.gif` e `/upload/avatar.webp`, porém **não** serão aplicadas nas URI `/upload/avatar.mp4`, `/upload/avatar.jpg.avif` ou `/UPLOAD/avatar.jpg`.

```conf
location ~ /upload/.*\.(jpe?g|gif|webp)$ {
    ...
}
```

O modificador `~*` funciona da mesma forma que `~`. A diferença é que essa é *case-insensitive* e esta, não; ou seja, utilizando o modificador `~*`, a URI `/upload/avatar.jpg` e `/UPLOAD/avatar.jpg` serão processadas igualmente.

```conf
location ~* /upload/.*\.(jpe?g|gif|webp)$ {
    ...
}
```

O modificador `^~` determina que o NGINX deverá parar de pesquisar correspondências mais específicas e utilizar essa diretiva.

```conf
location ^~ /upload/.*\.(jpe?g|gif|webp)$ {
    # Para aqui e não continua
}

location ~* \.(jpe?g|gif|webp)$ {
    ...
}
```

Caso nenhum modificador seja utilizado, o Nginx comparará o início da URI com parte da URL. No exemplo abaixo, as configurações da diretiva `location` serão aplicadas em `/upload`, `/upload/avatar.jpg` e `/uploadsssss/avatar.jpg`, por exemplo. Esse modo diferencia maiúscula de minúscula.

```conf
location /upload {
    ...
}
```

-------------

**Capturando grupos de <abbr title="Expressões Regulares">RegEx</abbr>**

Na diretiva `location`, você pode capturar os grupos definidos em sua <abbr title="Expressão Regular">RegEx</abbr>. Isso é útil para tornar as configurações dinâmicas.

```conf
root /var/www/html;

location ~* /profile/([^/]+).jpg {
    alias /dev/vda2/users/$1/avatar.jpg;
}
```

No exemplo acima, informamos que requisições com a URI `/profile/qualquer-coisa.jpg` será apontada para o arquivo `/dev/vda2/users/qualquer-coisa/avatar.jpg`.

-------------

**Nomeando `location`**

Nomear a diretiva `location` é importante para reutilizá-la em outras partes do código ou quebrar as regras em pedaços para facilitar a manutenção e/ou leitura dos arquivos de configuração. A sintaxe é semelhante as que já foram mostradas. A diferença está no parâmetro `@`.

```conf
location / {
    try_files $uri $uri/ @opencart;
}

location ~ ^/sitemap.xml$ {
    try_files $uri @opencart;
}

location @opencart {
    rewrite ^/sitemap.xml$ /index.php?route=extension/feed/google_sitemap last;

    rewrite ^/(.+)$ /index.php?_route_=$1 last;
}
```

-------------

## map ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/modules/ngx_http_map_module.c#L381))

A diretiva `map` permite criar variáveis de acordo com um mapeamento feito através de valores *key:value* definidos nela. É útil para tornar as configurações dinâmicas.

No exemplo abaixo, utilizamos a variável `$http_user_agent`, que é nativa do Nginx, para capturar o *User Agent* do usuário e comparar com as chaves. Se a variável do Nginx for igual à chave, então define um valor na variável a ser criada.

```conf
# Syntax: map $variable_nginx $variable_name { ... }
# Default: —
# Context: http
#
map $http_user_agent $ua_blocked {
    default 0; # Parâmetro

    ~Jorgee 1; # Block "Jorgee", "One Jorgee Two"
    ~ZmEu   1;
    ~*Spam  1; # Block "SPAM", "One spam Two"
    ~*Scan  1;
    Scanner 1; # Block "Scanner", but not "PSR-Scanner"
    Wget    1;
    curl    1;
}

server {
    if ($ua_blocked) {
        return 403;
    }
}
```

No exemplo acima, se a variável `$http_user_agent` for igual à `Jorgee`, `ZmEu`, `Spam` etc., define o valor 1 à variável `$ua_blocked`. Caso valor nenhum coincida, utiliza o valor do parâmetro `default`.

Ao usar `~` ou `~*`, você indica que o Nginx deve tratar a chave como expressão regular *case-sensitive* e *case-insensitive*, respectivamente. Não usá-las, indica que a correspondência deve ser exata.

**Parâmetros do `map`**

<dl>
  <dt><b>default</b> <i>&lt;valor&gt;</i></dt>
  <dd>Define um valor padrão que retornará quando as regras não corresponderem às chaves definidas.</dd>

  <dt><b>include</b> <i>&lt;arquivo&gt;</i></dt>
  <dd>Inclui um arquivo com valores. Pode haver várias inclusões.</dd>

  <dt><b>volatile</b></dt>
  <dd>Indica que a variável não pode ser armazenada em cache.</dd>

  <dt><b>hostnames</b></dt>
  <dd>Indica que os valores de origem podem ser nomes de host com um prefixo ou máscara de sufixo.</dd>
</dl>

**Funcionamento do `map`**

Se houver mais de uma correspondência, o Nginx seguirá a ordem abaixo:

1. Valor *string* sem máscara;
2. Valor da *string* mais longa com uma máscara de prefixo, por exemplo: _*.example.com_;
3. Valor da *string* mais longa com uma máscara de sufixo, por exemplo, _mail.*_;
4. Primeiro valor encontrado com a expressão regular;
5. Valor do parâmetro `default`.

-------------

## return ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/modules/ngx_http_rewrite_module.c#L442-L509))

Ao utilizar essa diretiva, o *Nginx* irá parar o processamento e retornará um valor definido ou um redirecionamento de página.

No exemplo abaixo, ao utilizar o código de *status* *301* ou *302* ou apenas uma URL, o Nginx informará ao navegador que o cliente deve ser redirecionado para a URL informada.

Ao utilizar o código de *status* *444*, o Nginx fechará a conexão e não retornará uma resposta.

Em qualquer outro código de *status*, o Nginx retornará o texto como o corpo da resposta.

```conf
# Syntax: return code [text];
#         return [301|302] URL;
# Default: —
# Context: server, location, if
#

location /retorna {
    return 200 "Corpo da Mensagem";
}

location /movido-permanente {
    return 301 https://another-url.com;
}

location /movido-temporario {
    return $scheme://auth.$server_name; # Equivale a https://auth.valdeir.dev
}

location /fechado {
    return 444;
}
```

-------------

## rewrite ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/modules/ngx_http_rewrite_module.c#L298-L438))

A diretiva permite reescrever a URI internamente ou redirecionar o usuário para outra página utilizando o *status code* 301 ou 302. Caso haja mais de uma diretiva `rewrite`, a URI poderá ser substituída/processada até a última diretiva ou conforme o uso das *flags*.

```conf
# Syntax: rewrite regex replacement [flag];
# Default: —
# Context: server, location, if
#
rewrite ^/sitemap.xml$ /index.php?route=extension/feed/google_sitemap last;
```

**Flag (opcional)**

<dl>
  <dt><b>last</b></dt>
  <dd>Para o processamento atual e busca um bloco <code>location</code> que corresponda à nova URI.</dd>

  <dt><b>break</b></dt>
  <dd>Para o processamento atual e retorna a resposta para o usuário.</dd>

  <dt><b>redirect</b></dt>
  <dd>Informa ao navegador que o usuário deverá ser redirecionado se o parâmetro <code>replacement</code> iniciar com <i>https://</i>, <i>http://</i> ou a variável <code>$scheme</code>. O redirecionamento deverá ser feito com o <i>status code 302</i></dd>

  <dt><b>permanent</b></dt>
  <dd>Informa ao navegador que o usuário deverá ser redirecionado se o parâmetro <code>replacement</code> iniciar com <i>https://</i>, <i>http://</i> ou a variável <code>$scheme</code>. O redirecionamento deverá ser feito com o <i>status code 301</i>
</dl>

**Quando usar `last` ou `break`?**

Como explicado, o `last` buscará um novo bloco `location` para tratar a URI substituída. Caso utilize essa *flag* dentro do contexto `location`, o Nginx poderá entrar em um *loop infinito* e gerar o erro 500. Portanto, quando possível, opte por `break` quando estiver no contexto do `location`; e, `last`, `server`.

```conf
server {
    rewrite ^/upload/(.*)$ /upload/foto/$1 last;
}

location /static/(.*) {
    rewrite ^/static/(.*)$ /static/foto/$1 break; # O `break` evita o erro 500
}
```

 > **Atenção!** O grupo capturado na RegExp pode ser substituído ao usar `map`. Para resolver, você pode usar um nome para o grupo ou criar uma variável antes de usar a diretiva `rewrite`.

```conf
# Permite que o Nginx retorne uma imagem otimizada de acordo com o navegador
# Para saber mais, acesse o link <link-do-próximo-artigo>
#
map $uri $file_ext {
    ...
}

location /upload {
    set $file_extension $file_ext;

    rewrite ^/upload/(.*)\..+$ "/static/$1$file_extension" break;
}
```

-------------

## root ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/ngx_http_core_module.c#L4316-L4414))

Define o diretório para as requisições. No exemplo abaixo, ao acessar a URI `/css`, o Nginx carregará o arquivo da pasta `/dev/vda2/css/` em vez do diretório `/var/www/html`.

Caso a diretiva `root` não seja definida no escopo `location`, o Nginx buscará na diretiva `server` e caso não encontre, buscará na diretiva `http`.

```conf
server {
    # Syntax: root path;
    # Default: root html;
    # Context: http, server, location, if in location
    #
    root /var/www/html;

    location /css {
        root /dev/vda2/css;
    }
}
```

**Quando usar `alias` e `root`?**

O `root` adiciona a URI ao construir o caminho do arquivo, enquanto o `alias` modifica a URI. Além disso, o `alias` **não** pode estar dentro do contexto `location` nomeado e não pode usar a variável `$realpath_root`.

```conf
# Na requisição `/foto/luisa.jpg`, o Nginx carregará o arquivo
# /var/www/html/static/foto/luisa.jpg
#
location /foto {
    root /var/www/html/static;
}

# Na requisição `/imagem/sonza.jpg`, o Nginx carregará o arquivo
# /var/www/html/static/sonza.jpg
#
location /imagem {
    alias /var/www/html/static/;
}
```

-------------

## server ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/ngx_http_core_module.c#L2806-L2946))

Essa diretiva em bloco serve para criar um servidor virtual.

```conf
# Syntax: server { ... }
# Default: —
# Context: http
#
server {
    server_name valdeir.dev www.valdeir.dev;
}
```

-------------

## server_name ([fonte](https://github.com/nginx/nginx/blob/41a241b3ef74dbbe3d82ab2ebbe682919e4a0b90/src/http/ngx_http_core_module.c#L4215-L4312))

Define o nome/URL do servidor virtual criado. É possível definir mais de um nome quando separados por espaços. A diretiva, assim como `rewrite` e `location`, permite o uso de expressões regulares.

Criaremos abaixo um servidor virtual que receberá as requisições das URLS: 

- valdeir.dev
- cdn.valdeir.dev
- assets.valdeir.dev
- qualquer-coisa.valdeir.cdn

```conf
server {
    # Syntax:  server_name name ...;
    # Default: server_name "";
    # Context: server
    #
    server_name valdeir.dev ~(cdn|assets)\.valdeir\.dev$ *.valdeir.cdn;
}
```

> **Atenção!** Para utilizar RegExp, é necessário que o Nginx tenha sido compilado com suporte à biblioteca `PCRE`.

Através da diretiva `server_name`, você pode criar variáveis de acordo com os grupos capturados na sua expressão regular.

```conf
server {
    server_name ~^(www\.)?(?<domain>.+)$;

    location / {
        root /sites/$domain;
    }
}

server {
    server_name _;

    location / {
        root /sites/default;
    }
}
```

**Ordem na busca pelos servidor**

Quando há mais de um nome de servidor, o Nginx seguirá a seguinte ordem:

1. Procurar pelo nome exato (valdeir.dev);
2. Procurar pelo nome mais longo com o *wildcard* (`*`) como prefixo (*.valdeir.dev);
3. Procurar pelo nome mais longo com o *wildcard* (`*`) como sufixo (valdeir.*);
4. Procurar pela primeira ocorrência usando expressão regular.

> Observação! Você, obviamente, deve cadastrar corretamente os dados de CDN que redirecione o site para seu servidor.

-------------

## try_files ([fonte](https://github.com/nginx/nginx/blob/a64190933e06758d50eea926e6a55974645096fd/src/http/modules/ngx_http_try_files_module.c))

Essa diretiva verificará se um arquivo existe seguindo a ordem pré-definida pelo administrador. O processamento é feito semelhante ao das diretivas `alias` e `root`. Ela permite também que o cliente possa acessar uma pasta quando o valor terminar com uma barra (`/`). Isso é útil quando o desenvolvedor utiliza a diretiva `autoindex`. Se arquivo nenhum for encontrado, o o usuário será redirecionado para outra página, URL ou bloco `location` nomeado.

```conf
root /var/www/html;

autoindex on;

location /upload {
    # Syntax: try_files file ... uri;
    #         try_files file ... =code;
    # Default: —
    # Context: server, location
    #
    try_files $uri $uri/ /empty.gif =444;
}
```

No exemplo acima, ao acessar a requisição `/upload/avatar.webp`, o Nginx seguirá a seguinte ordem:

1. Verifica se o arquivo `/var/www/html/upload/avatar.webp` existe; se não existir,
2. Verifica se a pasta `/var/www/html/upload/avatar.webp/` existe;  se não existir,
3. Verifica se o arquivo `/var/www/html/empty.gif` existe;  se não existir,
4. Fecha a conexão sem uma resposta (usando o `=444`).

É possível também utilizar bloco `location` nomeado.

```conf
location / {
  try_files $uri $uri/ @opencart;
}

location @opencart {
  rewrite ^/sitemap.xml$ /index.php?route=extension/feed/google_sitemap last;
  rewrite ^/googlebase.xml$ /index.php?route=extension/feed/google_base last;
  rewrite ^/system/storage/(.*) /index.php?route=error/not_found last;

  rewrite ^/(.+)$ /index.php?_route_=$1 last;
}

location ~* (\.twig|\.tpl|\.ini|\.log|(?<!robots)\.txt)$ {
  deny all;
}
```

-------------

## Conclusão

Essas foram as diretivas mais utilizadas no Nginx. Nas próximas postagens, aprenderemos como aumentar a segurança e a performance do nosso site. Beleza? 👍