Zum Inhalt

cors: Korrektes CORS für NGINX, einschließlich Preflight und Vary

Installation

Sie können dieses Modul in jeder RHEL-basierten Distribution installieren, einschließlich, aber nicht beschränkt auf:

  • RedHat Enterprise Linux 7, 8, 9 und 10
  • CentOS 7, 8, 9
  • AlmaLinux 8, 9
  • Rocky Linux 8, 9
  • Amazon Linux 2 und Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-cors
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-cors

Aktivieren Sie das Modul, indem Sie Folgendes oben in /etc/nginx/nginx.conf hinzufügen:

load_module modules/ngx_http_cors_module.so;

Dieses Dokument beschreibt nginx-module-cors v1.0.0 veröffentlicht am 15. Aug. 2026.


CORS für NGINX, das die Teile übernimmt, die das add_header-Rezept falsch macht.

Warum nicht einfach add_header verwenden?

Das Rezept map $http_origin + add_header Access-Control-Allow-* ist das, das alle kopieren, und es ist auf vier konkrete Arten fehlerhaft:

  • add_header wird pro Ebene vererbt oder ersetzt. Sobald ein location einen einzigen eigenen Header hinzufügt, verschwinden still und leise alle add_header, die darüber gesetzt wurden — einschließlich Ihrer CORS-Header. Nichts warnt Sie.
  • Preflight benötigt einen Short-Circuit. Ein OPTIONS-Preflight muss mit einem 204 und den richtigen Headern beantwortet werden, ohne Ihre Anwendung zu erreichen. Das von Hand zu tun bedeutet einen if-Block, und dieser interagiert schlecht mit try_files und proxy_pass.
  • add_header greift nur bei einer Whitelist von Statuscodes, es sei denn, Sie übergeben always. Dadurch verlieren Ihre Fehlerantworten ihre CORS-Header, und der Browser meldet einen undurchsichtigen CORS-Fehler anstelle des 404 oder 502, der tatsächlich aufgetreten ist.
  • Vary: Origin wird vergessen. Wenn die Antwort vom Request-Origin abhängt und Sie das nicht angeben, wird jeder Shared Cache oder CDN vor Ihnen fröhlich die Antwort eines Origins an einen anderen ausliefern. Dies ist ein Cache-Poisoning-Bug und der häufigste in freier Wildbahn.

Dieses Modul erledigt alle vier korrekt, als Header-Filter plus einem Handler in der Preaccess-Phase, ohne if-Blöcke.

Synopsis

location /api/ {
    cors                on;
    cors_origin         https://app.example.com https://*.staging.example.com;
    cors_methods        GET HEAD POST PUT DELETE;
    cors_headers        Authorization Content-Type;
    cors_expose_headers X-Total-Count;
    cors_credentials    on;
    cors_max_age        86400;

    proxy_pass http://backend;
}

Ein Preflight sieht dann so aus und erreicht nie backend:

$ curl -i -X OPTIONS https://api.example.com/api/things \
    -H 'Origin: https://app.example.com' \
    -H 'Access-Control-Request-Method: PUT'
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
Vary: Origin, Access-Control-Request-Method

Direktiven

cors

Syntax: cors on | off;

Default: cors off;

Context: http, server, location, if in location

Aktiviert die CORS-Verarbeitung. Jede andere Direktive wird unabhängig vererbt, sodass ein verschachteltes location, das eine von ihnen setzt, nicht die übrigen verliert — was genau der Unterschied zu add_header ist.

cors_origin

Syntax: cors_origin * | any | <spec> ...;

Default: cors_origin *;

Context: http, server, location, if in location

Welche Origins auf die Ressource zugreifen dürfen. Zwei reservierte Wörter und drei Spec-Formen:

Wert Bedeutung
* Gibt ein wörtliches Access-Control-Allow-Origin: * aus. Für alle gleich, daher wird kein Vary: Origin hinzugefügt. Kann nicht mit cors_credentials on kombiniert werden.
any Reflektiert, welcher Origin auch immer ankommt. Credential-sicher, gibt Vary: Origin aus.
https://app.example.com Exakte Übereinstimmung, Groß-/Kleinschreibung wird nicht beachtet.
https://*.example.com Wildcard-Subdomain.
~^https://(a\|b)\.example\.com$ Regulärer Ausdruck. ~* für Groß-/Kleinschreibung wird nicht beachtet.

Mehrere Specs können aufgelistet werden. Wenn eine übereinstimmt, wird dieser Origin zurückgegeben. * und any können nicht mit anderen Werten gemischt werden.

Wildcards sind bewusst streng: Das * muss direkt nach :// kommen und von einem . gefolgt werden. https://*example.com wird beim Start abgelehnt, anstatt still und leise https://evilexample.com zu matchen. Eine Wildcard matcht eine beliebige Anzahl führender Labels (https://a.b.example.com matcht https://*.example.com), matcht aber nicht den bloßen Apex und ignoriert keinen Port — ein Origin mit einem Port benötigt einen exakten Eintrag oder einen regulären Ausdruck.

Origin: null — was ein sandboxed iframe, ein data:-Dokument oder eine file://-Seite sendet — wird nur durch einen expliziten null-Eintrag in der Liste gematcht, wenn cors_credentials on. Weder any noch ein regulärer Ausdruck werden es in diesem Fall matchen. null mit Credentials zu reflektieren würde jedem sandboxed Frame im Internet einen authentifizierten Lesezugriff auf die Antwort gewähren.

cors_methods

Syntax: cors_methods * | <method> ...;

Default: cors_methods GET HEAD POST OPTIONS;

Context: http, server, location, if in location

Der Access-Control-Allow-Methods-Wert, der bei einem Preflight gesendet wird. * kann nicht mit cors_credentials on kombiniert werden.

cors_headers

Syntax: cors_headers * | any | <name> ...;

Default: — (der Header wird weggelassen)

Context: http, server, location, if in location

Der Access-Control-Allow-Headers-Wert, der bei einem Preflight gesendet wird. any gibt die Access-Control-Request-Headers der Anfrage wörtlich zurück; der Header wird weggelassen, wenn die Anfrage keine angefordert hat. * kann nicht mit cors_credentials on kombiniert werden.

cors_expose_headers

Syntax: cors_expose_headers <name> ...;

Default: — (der Header wird weggelassen)

Context: http, server, location, if in location

Antwort-Header, die der Browser über die safelisted Menge hinaus für Skripte lesbar machen soll. Wird bei tatsächlichen Antworten gesendet, nicht bei Preflights.

cors_credentials

Syntax: cors_credentials on | off;

Default: cors_credentials off;

Context: http, server, location, if in location

Gibt Access-Control-Allow-Credentials: true aus und erlaubt Cookies und HTTP-Authentifizierung bei Cross-Origin-Anfragen.

Die CORS-Spezifikation verbietet die Kombination von Credentials mit einer Wildcard, und jeder Browser erzwingt dies — eine Konfiguration, die beides tut, ist also eine Konfiguration, deren CORS nie funktioniert. NGINX verweigert den Start, anstatt Sie das ausliefern zu lassen:

cors_credentials on;
cors_origin      *;      # nginx: [emerg] ... cannot be combined with "cors_origin *"

Verwenden Sie eine explizite Liste oder cors_origin any zum Reflektieren. Beachten Sie, dass any plus Credentials jeder Website erlaubt, authentifizierte Antworten von diesem Location zu lesen; es ist erlaubt und protokolliert eine Warnung beim Start.

cors_max_age

Syntax: cors_max_age <time>;

Default: — (der Header wird weggelassen)

Context: http, server, location, if in location

Wie lange ein Browser das Preflight-Ergebnis cachen darf. cors_max_age 0; ist ein sinnvoller Wert und wird ausgegeben; das Weglassen der Direktive lässt den Header weg.

cors_preflight

Syntax: cors_preflight on | off;

Default: cors_preflight on;

Context: http, server, location, if in location

Ob passende Preflights intern beantwortet werden sollen. Schalten Sie es aus, wenn Ihre Anwendung OPTIONS selbst implementiert und Sie nur die Antwort-Header hinzugefügt haben möchten.

Ein Preflight wird nur dann kurzgeschlossen, wenn es tatsächlich eines ist — ein OPTIONS, das sowohl Origin als auch Access-Control-Request-Method trägt — und der Origin übereinstimmt. Alles andere fällt unberührt durch, sodass WebDAV und anwendungsseitiges OPTIONS weiter funktionieren, und ein Preflight von einem nicht erlaubten Origin erhält einfach kein Access-Control-Allow-Origin, was den Browser dazu bringt, es abzulehnen.

Da der Handler in der Preaccess-Phase läuft, wird ein Preflight beantwortet, bevor auth_basic, auth_request und deny die Chance bekommen, es abzulehnen. Das ist beabsichtigt: Browser senden bei einem Preflight niemals Credentials, sodass jede Authentifizierung davor CORS vollständig brechen würde. Tatsächliche Anfragen an denselben Location werden weiterhin normal authentifiziert.

cors_vary

Syntax: cors_vary on | off;

Default: cors_vary on;

Context: http, server, location, if in location

Ob Vary: Origin ausgegeben werden soll, wenn die Antwort vom Request-Origin abhängt.

Lassen Sie dies eingeschaltet. Es wird immer dann ausgegeben, wenn die Policy origin-abhängig ist — einschließlich, wenn der Origin nicht übereinstimmte und wenn die Anfrage überhaupt keinen Origin trug, weil eine gecachte Kopie dieser Antwort niemals an eine Anfrage wiedergegeben werden darf, deren Origin andere Header erzeugt hätte. Es wird bewusst nicht für ein statisches cors_origin * ausgegeben, das für alle gleich ist und nicht variiert.

Schalten Sie es nur aus, wenn Sie wissen, dass kein Shared Cache vor diesem Location sitzt oder Ihr CDN bereits auf Origin keyt.

Hinweise

  • Ein bereits in der Antwort vorhandenes Vary wird erweitert, niemals ersetzt: Ein Upstream, der Vary: Accept-Language sendet, kommt als Vary: Accept-Language, Origin heraus, und mehrere Upstream-Vary-Zeilen werden zu einer zusammengeführt. Ein Vary: * wird in Ruhe gelassen, da es gemäß RFC 9110 bereits alles subsumiert.
  • Zwei Dinge fügen ihr Vary nach diesem Modul hinzu und kommen daher als separate Feldzeile an, anstatt eingefügt zu werden: ein add_header Vary ... im selben Location und das Vary: Accept-Encoding von gzip_vary on. Mehrere Vary-Feldzeilen bedeuten genau dasselbe wie eine kombinierte Zeile (RFC 9110 §5.3) und jeder Cache kommt damit zurecht, das ist also korrekt — nur nicht ordentlich. Laden Sie nginx-module-compression-vary, wenn Sie alles in einer Zeile zusammengeführt haben möchten.
  • Setzen Sie CORS-Header nicht auch noch mit add_header im selben Location. Dieses Modul ersetzt jedes Access-Control-Allow-Origin, das es findet — zwei davon sind ein harter Fehler in jedem Browser —, aber die anderen Header würden dupliziert werden.
  • Header werden auch bei 304- und 206-Antworten sowie bei Fehlern ausgegeben.
  • Subrequests werden übersprungen, was dem Verhalten von add_header entspricht.