Websites & apps

Fix CORS errors: "No 'Access-Control-Allow-Origin' header"

Understand browser CORS errors when a frontend calls an API on another domain, and fix them correctly in the app, Nginx or Apache, including preflight requests and credentials.

IntermediateLinux VPS · cPanel hosting3 min read

Access to fetch at 'https://api.example.com' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present. Browsers block JavaScript from reading responses from another origin unless that server explicitly allows it. The fix always belongs on the API server, not in the frontend.

Before you start

  • The exact origin of your frontend (scheme + host + port, e.g. https://app.example.com).
  • Access to the API's code or its web server configuration.

How CORS works in short

  • An origin is scheme + host + port; https://example.com and https://www.example.com are different origins.
  • For simple GET requests the browser sends the request and checks the response for Access-Control-Allow-Origin.
  • For requests with JSON bodies, custom headers (e.g. Authorization) or methods like PUT/DELETE, the browser first sends an OPTIONS preflight; the server must answer it with the allowed methods and headers.
  • Tools like curl and Postman ignore CORS, which is why the API "works" there.

Preferred: configure CORS in the application

Frameworks have built-in or standard CORS support that handles preflights correctly:

  • Laravel: config/cors.php (allowed_origins, paths).
  • Express: the cors middleware, e.g. app.use(cors({ origin: ['https://app.example.com'] })).
  • Django: django-cors-headers with CORS_ALLOWED_ORIGINS.
  • Flask: Flask-CORS. Strapi: config/middlewares CORS settings.

Alternatively: Nginx

location /api/ {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "https://app.example.com" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
        add_header Access-Control-Max-Age 86400 always;
        return 204;
    }
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    add_header Vary Origin always;
    proxy_pass http://127.0.0.1:3000;
}

always makes Nginx add the headers to error responses too, so you see the real error instead of a misleading CORS one. Do not set CORS headers in both Nginx and the app: duplicated headers are rejected by browsers.

Alternatively: Apache / .htaccess

<IfModule mod_headers.c>
    Header always set Access-Control-Allow-Origin "https://app.example.com"
    Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header always set Access-Control-Allow-Headers "Authorization, Content-Type"
    Header always append Vary Origin
</IfModule>

Make sure OPTIONS requests are answered with a 2xx status; add a rewrite rule returning 204 for OPTIONS if the app rejects them.

Cookies and credentials

If the frontend sends cookies (credentials: 'include'):

  • The server must send Access-Control-Allow-Credentials: true.
  • Access-Control-Allow-Origin must be the exact origin, not *.
  • Cookies need SameSite=None; Secure for cross-site use.

Allowing several origins

The header accepts only one origin. To allow several, the app (or an Nginx map on $http_origin) must check the request's Origin against an allowlist and echo it back, with Vary: Origin. Avoid reflecting any origin blindly, especially with credentials.

Check the fix

curl -si -X OPTIONS https://api.example.com/api/items -H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: POST' -H 'Access-Control-Request-Headers: content-type' | grep -i access-control

You should see the allow headers in the response.

Troubleshooting

  • CORS error but the real problem is a 500 or 404: error responses often lack CORS headers. Check the Network tab's status code.
  • The value has multiple origins / header contains multiple values: CORS headers set in two places.
  • Works for GET, fails for POST: the preflight is not handled.

CORS FAQ

Is Access-Control-Allow-Origin: * safe?

For truly public, read-only data without credentials, yes. For anything with authentication, list specific origins.

Can I fix CORS in the frontend?

No. Only the server can grant access; the alternative is to proxy API calls through your own domain.

Does CORS protect my API from scripts?

No, it only restricts browsers. Protect APIs with authentication and rate limits.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.