Die Fehlermeldung in der Browser-Konsole lautet meist so: „No 'Access-Control-Allow-Origin' header is present on the requested resource“. Der Browser hat eine Datei von einer anderen Domain angefordert und vermisst die Erlaubnis, sie zu verwenden. Diese Erlaubnis ist ein HTTP-Header, und er muss von dem Server kommen, der die Datei ausliefert, nicht von der Seite, die sie lädt. Hier erfahren Sie, wann Sie ihn brauchen, wie Sie ihn in der .htaccess setzen (alle Beispiele sind auf einem Apache-Server getestet) und warum dieselben Zeilen bei vielen Hostern für Bilder, Schriften und Skripte nichts bewirken.

Warum Dateien von anderen Domains einbinden?

Meistens liegt alles, was eine Website braucht, auf ihrer eigenen Domain. Es gibt aber gute Gründe für Ausnahmen, und oft gehört die andere Domain Ihnen selbst:

  • Schriftdateien liegen auf einer Subdomain oder in einem CDN, etwa cdn.ihre-domain.at, und werden per @font-face eingebunden.
  • Ein Skript auf Ihrer Website holt Daten per fetch() von einer Schnittstelle (API) auf einer anderen Domain.
  • Ein Bild von einer anderen Domain wird in ein <canvas>-Element gezeichnet und weiterverarbeitet.

Ein normales Bild, ein Stylesheet oder ein Skript, das Sie mit <img>, <link> oder <script src> einbinden, lädt der Browser auch ohne CORS-Header. CORS kommt ins Spiel, wenn Code den Inhalt einer fremden Antwort lesen oder verwenden will, und bei Webfonts (MDN: Welche Anfragen verwenden CORS?).

Was ist ein Cross-Origin-Request (CORS)?

Ein „Origin“ (Ursprung) besteht aus Protokoll, Domain und Port: https://www.example.com und https://shop.example.com sind zwei verschiedene Ursprünge, ebenso http:// und https:// derselben Domain. Fordert eine Seite etwas von einem anderen Ursprung an, ist das ein Cross-Origin-Request. Browser lassen ein Skript die Antwort darauf nur dann lesen, wenn der andere Server das ausdrücklich erlaubt. Dieses Erlauben heißt Cross-Origin Resource Sharing, kurz CORS.

Bei Anfragen, die Daten ändern können (etwa PUT oder DELETE, oder mit eigenen Headern), fragt der Browser vorher mit einer OPTIONS-Anfrage nach, ob das erlaubt ist („Preflight“). Erst wenn die Antwort passt, schickt er die eigentliche Anfrage.

Wichtig für die Einordnung: CORS schützt nicht Ihren Server, sondern lockert eine Schutzregel des Browsers. Eine einfache Anfrage schickt der Browser trotzdem ab; er gibt nur die Antwort nicht an das Skript weiter. Und wer mit einem Werkzeug wie curl anfragt, kümmert sich gar nicht um CORS. Ein CORS-Header ist also nie ein Ersatz für Zugangsschutz.

Eine externe Ressource für CORS zulassen

Den Header Access-Control-Allow-Origin setzt der Server, der die Ressource ausliefert. Liegt die Datei auf Ihrem eigenen Server oder Ihrer eigenen Subdomain, können Sie ihn selbst setzen. Liegt sie bei einem fremden Anbieter, kann nur dieser Anbieter ihn setzen; Ihre eigene .htaccess hilft dann nicht. Die Alternative ist, die Datei selbst zu hosten.

Der Header kennt drei sinnvolle Werte:

  • *: Jede Website darf die Datei verwenden. Das passt für öffentliche Dateien wie Schriften oder frei zugängliche Daten.
  • Genau ein Ursprung, zum Beispiel https://www.example.com. Eine Liste mehrerer Domains ist nicht erlaubt; wie Sie trotzdem mehrere zulassen, steht weiter unten.
  • Für Anfragen mit Anmeldedaten (Cookies) ist * ausgeschlossen: Der Browser verweigert die Antwort dann (MDN: Access-Control-Allow-Origin).

CORS-Header in .htaccess konfigurieren

Bearbeiten der Serverkonfiguration

Auf einem Apache-Server können Sie den Header in der .htaccess-Datei setzen, zum Beispiel im Stammverzeichnis Ihrer WordPress-Installation. Voraussetzung ist das Apache-Modul mod_headers. Diese Zeilen erlauben jedem Ursprung den Zugriff:

<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>

Wenn Sie nur einen bestimmten Ursprung zulassen möchten, ersetzen Sie * durch diesen Ursprung, mit Protokoll und ohne Schrägstrich am Ende:

<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://www.example.com"
</IfModule>

Beide Varianten wirken wie beschrieben, haben aber eine Nebenwirkung: Der Header landet auf jeder Antwort aus diesem Verzeichnis, auch auf WordPress-Seiten und der WordPress-Schnittstelle unter /wp-json/. Diese setzt ihre CORS-Header bereits selbst. Apache fügt Ihren Header dann zusätzlich ein, die Antwort enthält Access-Control-Allow-Origin zweimal, und Browser lehnen sie ab („contains multiple values“). Die Apache-Dokumentation beschreibt genau diesen Fall für Header, die ein Skript selbst setzt (mod_headers). Beschränken Sie den Header deshalb auf die Dateitypen, um die es geht:

<IfModule mod_headers.c>
  <FilesMatch "\.(woff2?|ttf|otf|eot|svg|json)$">
    Header set Access-Control-Allow-Origin "*"
  </FilesMatch>
</IfModule>

Mehrere Domains erlauben

Da der Header nur einen Ursprung enthalten darf, prüft der Server den mitgeschickten Origin und gibt ihn zurück, wenn er auf Ihrer Liste steht. Zusätzlich gehört Vary: Origin in die Antwort, damit Caches nicht die Antwort für die eine Domain an die andere ausliefern:

<IfModule mod_headers.c>
  <FilesMatch "\.(woff2?|ttf|otf|eot|svg|json)$">
    SetEnvIf Origin "^https://(www\.example\.com|shop\.example\.com)$" CORS_ORIGIN=$0
    Header set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN
    Header merge Vary Origin
  </FilesMatch>
</IfModule>

Ein Ursprung, der nicht auf der Liste steht, bekommt keinen Access-Control-Allow-Origin-Header, und der Browser blockiert den Zugriff. Die Punkte in den Domains sind mit \. maskiert, und ^ und $ sorgen dafür, dass nur genau diese Ursprünge passen.

Erlauben weiterer Header und Methoden

Für den oben beschriebenen Preflight brauchen Sie zusätzlich die erlaubten Methoden und Header. Das betrifft vor allem Schnittstellen, die per fetch() mit PUT, DELETE, JSON-Inhalten oder eigenen Headern angesprochen werden:

<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://www.example.com"
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header set Access-Control-Allow-Headers "X-Requested-With, Content-Type"
</IfModule>

Für Schriften, Bilder und Dateien, die nur gelesen werden, sind diese beiden Zeilen nicht nötig.

Wenn nichts passiert: mod_headers und IfModule

<IfModule mod_headers.c> sorgt dafür, dass die Zeilen still übersprungen werden, wenn das Modul nicht geladen ist: kein Fehler, aber auch kein Header. Ohne den IfModule-Block bricht die Website in diesem Fall mit einem Fehler 500 ab. Ob Ihr Header wirklich ausgeliefert wird, sehen Sie mit curl (Adresse einer echten Datei einsetzen):

curl -sI -H "Origin: https://www.example.com" https://ihre-domain.at/wp-content/fonts/schrift.woff2

In der Antwort muss eine Zeile access-control-allow-origin: stehen. Fehlt sie, lesen Sie den nächsten Abschnitt.

Warum die .htaccess bei Bildern, Schriften und Skripten oft nichts bewirkt

Viele Hosting-Plattformen stellen den Webserver nginx vor Apache. nginx liefert statische Dateien wie Bilder, Schriften, CSS und JavaScript direkt aus, und Apache sieht diese Anfragen nie. Ihre .htaccess wird dann für genau die Dateien nicht gelesen, um die es bei CORS meistens geht. Für WordPress-Seiten gilt sie weiterhin.

Auf dieser Website ist das messbar: Die .htaccess enthält eine Regel, die JavaScript-Dateien einen Monat Cache-Dauer gibt. Ausgeliefert werden sie mit einem Jahr, und zwar auch direkt vom Server ohne Cloudflare dazwischen. Die Regel greift also nicht, weil nginx die Dateien selbst ausliefert. So gehen Sie je nach Plattform vor:

  • Cloudways: In den Application Settings gibt es den Schalter „CORS Header“. Laut Cloudways gilt er für alle Domains der Anwendung, unterstützt nur den Wert * und hat Vorrang vor Einträgen in der .htaccess (Cloudways-Anleitung).
  • Plesk: Standardmäßig liefert nginx die statischen Dateien aus. Eigene Antwort-Header tragen Sie unter Websites & Domains → Einstellungen für Apache & nginx im Feld „Additional headers“ (deutsch „Zusätzliche Header“) ein, im Format Name: Wert (Plesk-Dokumentation). Prüfen Sie das Ergebnis danach mit curl an einer Schriftdatei.
  • Nur nginx, ohne Apache: Dort gibt es keine .htaccess. Der Header kommt mit add_header in die Server-Konfiguration, was bei Shared Hosting meist nur der Anbieter kann. Zwei Eigenheiten laut nginx-Dokumentation: Ohne den Zusatz always wird der Header nur bei erfolgreichen Antworten gesendet, und ein location-Block mit eigenem add_header übernimmt die Header der übergeordneten Ebene nicht.
  • Cloudflare davor: Cloudflare reicht den Header Ihres Servers durch und speichert Antworten je Origin getrennt im Cache, so dass auch die Variante mit mehreren Domains funktioniert (Cloudflare: Cache Keys). Kommt der Header vom Server nicht an, können Sie ihn auch bei Cloudflare mit einer „Response Header Transform Rule“ setzen.

Fragen Sie im Zweifel Ihren Hoster, ob statische Dateien von nginx ausgeliefert werden. Die Antwort entscheidet, wo der Header hingehört.

Wichtiger CORS Sicherheitshinweis

* ist für öffentliche Dateien unbedenklich: Eine Schrift oder ein frei zugängliches JSON kann ohnehin jeder herunterladen. Heikel wird es bei Antworten, die vom Login abhängen. Dort gehört nie * hin und auch keine Regel, die jeden anfragenden Ursprung einfach zurückgibt, sondern eine feste Liste, wie im Beispiel mit mehreren Domains. Den Wert null sollten Sie nie verwenden: Ihn kann sich jede Website verschaffen.

Indem Sie den Header nur für die Dateien setzen, die tatsächlich von anderen Domains gebraucht werden, und nur für die Ursprünge, die sie brauchen, laden externe Ressourcen korrekt, ohne dass Sie mehr freigeben als nötig.