---
title: Migrating from nginx
description: Super's configuration is close to nginx's
---
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.page/luizfonseca/super/llms.txt
> Use this file to discover all available pages before exploring further.

Super's configuration is close to nginx's: `host` blocks are `server` blocks, and `location` blocks are `location` blocks. This page covers the differences, for a configuration file and for Kubernetes (ingress-nginx).

## Concepts

| nginx | Super |
| --- | --- |
| `server { server_name ... }` | `host "<name>" { }` (one name per host) |
| `listen 443 ssl` | `server.https_address`, or a host's `listen` |
| `location /api { }` | `location "/api" { }` |
| `upstream app { server ...; }` and `proxy_pass` | `upstream "<host:port>" { }` blocks in the location |
| `ssl_certificate`, `ssl_certificate_key` | `tls { certificate { pem, key } }`, or `lets_encrypt` |
| `stream { }` | `tcp` blocks |
| `nginx -s reload` | [Auto reload](/configuration/auto-reload), or `SIGHUP` (see [Restarts and upgrades](/configuration/upgrades)) |

## A host

```nginx
upstream app {
  server 10.0.0.1:8080 weight=3;
  server 10.0.0.2:8080;
}

server {
  listen 80;
  server_name app.example.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  http2 on;
  server_name app.example.com;
  ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
  client_max_body_size 10m;

  location / {
    proxy_pass http://app;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}
```

```hcl
host "app.example.com" {
  tls {
    certificate {
      pem = "/etc/letsencrypt/live/app.example.com/fullchain.pem"
      key = "/etc/letsencrypt/live/app.example.com/privkey.pem"
    }
  }

  location "/" {
    upstream "10.0.0.1:8080" { weight = 3 }
    upstream "10.0.0.2:8080" {}

    body_limit {
      max = "10MiB"
    }
  }
}
```

What you no longer write:

- **The redirect to HTTPS**: hosts with TLS redirect HTTP requests (`308`).
- **HTTP/2** is on for HTTPS.
- **`Host`**: the client's `Host` is forwarded as it is. (nginx sends the upstream's name unless `proxy_set_header Host $host`.) To send another one, set it on the upstream: `headers { add = { "Host" = "..." } }`.
- **Forwarded headers**: `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host` and `X-Real-IP` are set. Behind another proxy, list it in `server.trusted_proxies` (as `set_real_ip_from`).
- **WebSockets** are proxied without `Upgrade`/`Connection` headers.
- **Certificates from certbot** can stay: the files are checked every 10 seconds, so a renewal is served without a reload. Or let Super order them with `lets_encrypt`, and drop certbot.

Mind the defaults that differ:

- **Body size**: nginx refuses bodies over 1 MB unless `client_max_body_size` says otherwise. Super has no limit unless a location has a `body_limit`.
- **Locations** match their path and everything below it: `location "/api"` serves `/api` and `/api/users`, but not `/apis` (nginx's `location /api` serves `/apis` too). The most specific location wins. `location = /x` is `location "/x" { exact = true }`, and `location ~ ^/x` is `location "^/x"` (a [regex location](/routing/regex-locations), matched with case, tried in order before paths). `~*` and `^~` aren't supported: use `(?i)` in the regex to ignore case.
- **`proxy_pass` with a path** (`location /api/ { proxy_pass http://app/v2/; }`) replaces the matched prefix; in Super, that's [`rewrite`](/routing/upstreams#rewriting-paths) with `strip_prefix = "/api"` and `add_prefix = "/v2"`. Without it, the path is forwarded unchanged.

## Directives

| nginx | Super |
| --- | --- |
| `limit_req_zone` / `limit_req` | [`rate_limit { requests, per, burst }`](/plugins/rate-limit), per client (as `burst=... nodelay`: requests over the limit are refused, not delayed) |
| `client_max_body_size` | [`body_limit { max }`](/plugins/body-limit) |
| `auth_basic` | [`basic_auth { user, pass }`](/plugins/basic-auth) (one user, no htpasswd file) |
| `add_header` | `headers { response { add = { ... } } }` |
| `proxy_hide_header` | `headers { response { remove = [...] } }` |
| `proxy_set_header` | `headers { add = { ... } }` in an `upstream` |
| `proxy_cache` | [`cache { }`](/use-cases/cache) |
| `keepalive_timeout`, `client_header_timeout` | `server.timeouts.idle_secs` |
| `client_body_timeout` | `server.timeouts.read_secs`, or per host/location |
| `send_timeout` | `server.timeouts.write_secs`, or per host/location |
| `proxy_connect_timeout`, `proxy_read_timeout`, `proxy_send_timeout` | `timeouts { upstream { connect_secs, read_secs, write_secs } }`, in the server or per host/location |
| `access_log` | JSON access logs on stdout (see [Logging](/configuration/logging)) |
| `stub_status` | The [admin API](/configuration/admin): Prometheus metrics and live stats |
| `max_fails`, `fail_timeout` | [`health_check { interval_secs }`](/routing/upstreams#health-checks): a TCP connection to each upstream, unhealthy after 2 failures |
| `least_conn` | [`load_balancing = "least_connections"`](/routing/upstreams#least-connections), with `weight` |
| `ip_hash`, `hash` | Not available (weighted round robin or least connections) |
| `proxy_next_upstream`, `proxy_next_upstream_tries` | [`retry { on, attempts }`](/routing/upstreams#retries) (`error` and `timeout`: `connect`; `http_502`: `"502"`) |
| `split_clients` | [`traffic_split`](/routing/traffic-split) blocks, each client kept on its side by a cookie or its address |
| `mirror` | [`mirror { upstream ... }`](/routing/mirror), with `percent` and a limit on body sizes |
| `auth_jwt` (NGINX Plus) | [`jwt { ... }`](/plugins/jwt): a secret, a public key or a JWKS, claims as headers |
| `rewrite ^/old/(.*)$ /new/$1 break;` | [`rewrite`](/routing/upstreams#rewriting-paths) `{ match = "^/old/(.*)$", replace = "/new/$1" }` (redirecting rewrites, `last`/`redirect`/`permanent`, aren't supported) |
| `gzip on`, `brotli on` | [`compress { }`](/plugins/compress), with `level` for `gzip_comp_level` (`gzip_types`: text, JSON, JavaScript, fonts and SVG are compressed) |
| `return 301 https://example.com$request_uri;` | [`redirect`](/plugins/redirect) with `to = "https://example.com{uri}"` and `status = 301` |
| `return 503 "Back soon";` | [`respond`](/plugins/respond) with `status = 503` and `body = "Back soon"` |
| `auth_request /auth;` with `auth_request_set` | [`forward_auth { url, response_headers }`](/plugins/forward-auth) |
| `allow`/`deny` | [`ip_filter { allow, deny }`](/plugins/ip-filter): `deny` wins, whatever the order |
| `root`, `alias`, `index`, `try_files $uri /index.html` | [`static_files { directory, index, fallback }`](/plugins/static-files) |

## TCP (`stream`)

```nginx
stream {
  server {
    listen 5433 ssl;
    ssl_certificate     /certs/db.pem;
    ssl_certificate_key /certs/db.key;
    proxy_pass 10.0.0.5:5432;
  }
}
```

```hcl
host "db.example.com" {
  tls {
    certificate {
      pem = "/certs/db.pem"
      key = "/certs/db.key"
    }
  }

  tcp "postgres" {
    listen = "0.0.0.0:5433"
    upstream "10.0.0.5:5432" {}
  }
}
```

A `tcp` block at the top level forwards plain TCP; `tls = "passthrough"` forwards TLS untouched, chosen by name (as `ssl_preread`). See [TCP](/routing/tcp).

## Kubernetes (ingress-nginx)

ingress-nginx has been retired by the Kubernetes project. Super serves the same `Ingress` resources (see [Kubernetes](/installation/kubernetes)); set `ingressClassName: proksi`, and replace the `nginx.ingress.kubernetes.io/*` annotations:

| ingress-nginx annotation | Super |
| --- | --- |
| `ssl-redirect`, `force-ssl-redirect` | Built in; `proksi.ingress/tls: "false"` for plain HTTP |
| `proxy-body-size` | `body_limit { max = "..." }` in `proksi.ingress/blocks` |
| `limit-rps`, `limit-rpm`, `limit-burst-multiplier` | `rate_limit { ... }` in `proksi.ingress/blocks`, with `burst` = `requests × (multiplier − 1)` (ingress-nginx's default multiplier is 5) |
| `auth-type: basic`, `auth-secret` | `basic_auth {}` in `proksi.ingress/blocks`, and `proksi.ingress/auth-secret`, naming a `Secret` labelled `proksi.ingress/auth` with `user` and `pass` keys (not an htpasswd file) |
| `configuration-snippet` (`more_set_headers`) | `headers { response { add = { ... } } }` in `proksi.ingress/blocks` |
| `proxy-connect-timeout`, `proxy-read-timeout`, `proxy-send-timeout` | `timeouts { upstream { connect_secs, read_secs, write_secs } }` in `proksi.ingress/blocks` |
| `service-upstream` | `proksi.ingress/upstream: service` |
| `rewrite-target` (with `use-regex`) | `rewrite { match, replace }` in `proksi.ingress/blocks`, e.g. `match = "^/api(/\|$)(.*)"` and `replace = "/$2"`; or `rewrite { strip_prefix = "/api" }` |
| `backend-protocol`, `affinity`, `whitelist-source-range` | Not available |

cert-manager works as before, including HTTP-01 challenges (set the solver's class to `proksi`, see [cert-manager](/installation/kubernetes#cert-manager)).
