1. Introduction

NGINX is a server that can be useful in many situations. From a forward, reverse, or even mail proxy to load-balancing, it’s fairly universal. Still, being widely used as a Web server, NGINX provides all options that one might expect from a modern implementation of HTTP(S).

In this tutorial, we look at ways to control origin limitations in NGINX. First, we briefly refresh our knowledge about the concept of origins in the Web and related issues. After that, we show a quick example of allowing requests from any origin. Next, we limit permissions only to simple requests. Finally, we narrow down the scope further and introduce a way to query the current configuration.

We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15. It should work in most POSIX-compliant environments unless otherwise specified.

2. Web Origin Issues

The Same Origin Policy (SOP) has been introduced to control the way one origin interacts with another. In other words, this is also known as a cross-origin policy.

As a simple example, scripts from http://gerganov.com need special permissions to request data from https://gerganov.com due to the difference in protocols. Should we attempt such an operation without proper setup, the request fails and we can often see an error in the Web browser console.

Let’s check a typical error from JavaScript:

Cross Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://gerganov.com/.

Similarly, XMLHttpRequest can produce related errors:

Access to XMLHttpRequest at 'https://gerganov.com' from origin 'http://gerganov.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

To ensure we can or can’t get data across origins, the so-called Cross-Origin Resource Sharing (CORS) policy might send the necessary HTTP headers. When it comes to NGINX, we add the latter via the add_header directive. In any case, to do this, NGINX needs the headers module.

Notably, headers can differ between separate request types.

3. NGINX Allow All Origins

Despite best practices, in many cases, servers are configured with a basic blanket permission for all cross-origin requests.

In the case of NGINX, we can use the basic location block. Critically, NGINX only uses one location directive, so any setup with multiple is ambiguous and might pick the wrong one:

$ cat /etc/nginx/conf.d/default.conf
location / {
  add_header 'Access-Control-Allow-Origin' '*';
}

Further, opening up NGINX like this not only exposes the content to possible exploitation but also enables different kinds of attacks on the server itself. This is the main reason to follow the Same Origin Policy (SOP) in the first place.

Importantly, we can decide on a specific origin or use a server-side script to dynamically read the Origin header values and decide on an action.

In any case, not having the Access-Control-Allow-Origin header at all means we don’t allow CORS on the server.

4. NGINX Simple Request Origin Headers

Simple HTTP requests are a direct way to check cross-origin policies, but might be refused in case the configuration doesn’t explicitly mention them:

In such instances, our responsibility would be to check the response for a CORS-specific problem.

To allow only all cross-origin GET and POST requests, we again use location, but this time limit the allowed methods:

$ cat /etc/nginx/conf.d/default.conf
location / {
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Methods' 'GET, POST';
}

Thus, we employ add_header to ensure two important headers are in place with the correct values:

  • Access-Control-Allow-Origin expects * any origin
  • Access-Control-Allow-Methods allows only GET and POST for cross-origin requests

Further, we can limit CORS headers by the location:

location ~* \.(x|ox|0x)$ {
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Methods' 'GET, POST';
}

However, even this setup can turn out to be quite limiting in terms of newer request methods.

5. NGINX Preflight Request Origin Headers

Akin to checking the options of a given channel before initiating communication, HTTP has so-called preflight requests. In short, preflight is a separate request type with a request method of OPTIONS for querying currently supported features and the cross-origin policy of the server.

In particular, we can get this data, so the server can share it before it receives an actual resource request:

OPTIONS / HTTP/1.1
Host: gerganov.com:443
[...]
Origin: http://gerganov.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-requested-with

Thus, https://gerganov.com (as indicated by the port) receives a query from an Origin of http://gerganov.com. As long as we permit OPTIONS as a cross-origin method, this request shouldn’t pose a problem, since no resources are requested yet. Instead, the client only checks for supported methods and features.

A server can then respond with the appropriate CORS headers:

$ cat /etc/nginx/conf.d/default.conf
location / {
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Methods' 'GET, POST, HEAD, OPTIONS';
   if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Credentials' 'true';
      add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
      add_header 'Access-Control-Max-Age' 57542400;
      add_header 'Content-Type' 'text/plain charset=UTF-8';
      add_header 'Content-Length' 0;
      return 204;
   }
}

Here, we expanded our location block with an (evil) if statement that checks the request method and adds more headers if it’s equal to OPTIONS:

  • Access-Control-Allow-Methods now permits cross-origin OPTIONS requests (critical)
  • Access-Control-Allow-Credentials allows credentials as part of cross-origin requests
  • Access-Control-Allow-Headers allows a certain set of fairly standard headers

Further, we augment the response some more and return a 204 No content status, indicating success.

6. Summary

In this article, we discussed the NGINX header configuration related to the Same Origin Policy (SOP).

In conclusion, since CORS headers are an important security measure and permission mechanism, knowing how to configure them for a ubiquitous Web server such as NGINX can be critical.