172 lines
5.7 KiB
HTML
172 lines
5.7 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="author" content="Reimar" />
|
|
<meta name="viewport" content="width=device-width" />
|
|
<title>Handling the Upgrade-Insecure-Requests header in nginx</title>
|
|
<link rel="stylesheet" href="assets/style.css" />
|
|
<link rel="alternate" href="atom.xml" type="application/atom+xml" />
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>Handling the Upgrade-Insecure-Requests header in nginx</h1>
|
|
<span class="published">Published on <time datetime="2025-08-02">August 2nd, 2025</time></span>
|
|
</header>
|
|
|
|
<p>
|
|
Today it is considered a de facto security standard for the
|
|
web, that all HTTP requests automatically be redirected to
|
|
HTTPS to prevent Man-in-the-middle attacks. Of course, this
|
|
is a good standard to conform to, but there can also be
|
|
benefits with providing an insecure HTTP version of your
|
|
site as a fallback for users who, for one reason or another,
|
|
are not able to use HTTPS. You can read more about that
|
|
<a href="https://1mb.club/blog/https-redirects/">here</a>.
|
|
</p>
|
|
<p>
|
|
Luckily, the web specifications have a way for the client to
|
|
tell the server whether or not it wants insecure connections
|
|
to be upgraded or not: The
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Upgrade-Insecure-Requests">
|
|
<code>Upgrade-Insecure-Requests</code><!--
|
|
--></a>
|
|
header.
|
|
By setting this header to <code>1</code>, the client can
|
|
indicate to the server, that it wants any insecure HTTP
|
|
requests to automatically be upgraded to HTTPS. This has
|
|
also been implemented in all major browsers since 2018. This
|
|
article will show you how to handle this header using nginx.
|
|
</p>
|
|
|
|
<fieldset class="table-of-contents">
|
|
<legend>Table of contents</legend>
|
|
<a href="#configuring">Configuring nginx</a><br />
|
|
<a href="#cloudflare">Using Cloudflare</a><br />
|
|
<a href="#testing">Testing the configuration</a>
|
|
</fieldset>
|
|
|
|
<h2 id="configuring">Configuring nginx</h2>
|
|
<p>
|
|
In its essence, the configuration is very simple: For every
|
|
request, we want to check if the scheme is <code>http</code>
|
|
and the <code>Upgrade-Insecure-Requests</code> header is set
|
|
to <code>1</code>. If both of these conditions match, we
|
|
want to redirect to the same URL using HTTPS.
|
|
</p>
|
|
<p>
|
|
Sadly, you cannot have if-statements in nginx with multiple
|
|
conditions, so we will have to do a workaround. What we can
|
|
do is combine the current scheme with the value of the
|
|
header into a string and match that against a specific
|
|
value. This can be done using <code>map</code> in nginx:
|
|
</p>
|
|
<pre>
|
|
map "$scheme+$http_upgrade_insecure_requests" $upgrade {
|
|
default 0;
|
|
"http+1" 1;
|
|
}</pre>
|
|
<p>
|
|
Here we combine the two values into a string with a
|
|
<code>+</code> in the middle, and compare it against the
|
|
string <code>"http+1"</code>. If it matches, the
|
|
<code>$upgrade</code> variable is set to <code>1</code>,
|
|
otherwise <code>0</code>.
|
|
</p>
|
|
<p>
|
|
Now we just need to redirect based on this variable. This
|
|
must be done inside the <code>server</code> block, while
|
|
the variable must be defined in the global scope. Make sure
|
|
also that the server is set to listen on both port 80 and
|
|
443.
|
|
</p>
|
|
<pre>
|
|
if ($upgrade) {
|
|
return 301 https://$host$request_uri;
|
|
}</pre>
|
|
<p>
|
|
Putting it all together, we now have a functioning nginx
|
|
configuration which redirects properly based on this header:
|
|
</p>
|
|
<pre>
|
|
map "$scheme+$http_upgrade_insecure_requests" $upgrade {
|
|
default 0;
|
|
"http+1" 1;
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
listen 443 ssl;
|
|
|
|
server_name example.com;
|
|
|
|
location / {
|
|
# ...
|
|
}
|
|
|
|
if ($upgrade) {
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
}</pre>
|
|
|
|
<h2 id="cloudflare">Using Cloudflare</h2>
|
|
<p>
|
|
If you are using Cloudflare, you will have to make some
|
|
changes to this configuration. In this case, all requests
|
|
your web server receives will be either HTTP or HTTPS
|
|
depending on your Cloudflare configuration, regardless of
|
|
the actual user's request.
|
|
</p>
|
|
<p>
|
|
Luckily, Cloudflare sends a header along with its request, telling
|
|
whether the user requested HTTP or HTTPS. This one is called
|
|
<a href="https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-visitor">
|
|
<code>CF-Visitor</code><!--
|
|
--></a>,
|
|
and the value is actually a JSON string that could tell more
|
|
information about the request, but currently it only
|
|
contains a single key (the scheme). This makes it very easy
|
|
to handle, as we can just continue using string matching.
|
|
</p>
|
|
<pre>
|
|
map "$http_cf_visitor+$http_upgrade_insecure_requests" $upgrade {
|
|
default 0;
|
|
"{\"scheme\":\"http\"}+1" 1;
|
|
}</pre>
|
|
<p>
|
|
Changing the map-statement from before to the above should
|
|
make the configuration work with Cloudflare. Although this
|
|
solution is not completely future-proof, Cloudflare has kept
|
|
it like this for long enough, that I consider it pretty
|
|
safe. If they change it, HTTPS redirection will be
|
|
completely disabled.
|
|
</p>
|
|
|
|
<h2 id="testing">Testing the configuration</h2>
|
|
<p>
|
|
You can use curl to make sure the configuration works
|
|
properly. The <code>--head</code> option sends a
|
|
<code>HEAD</code> request and makes curl output the response
|
|
code and headers.
|
|
</p>
|
|
<pre>
|
|
curl --head http://example.com</pre>
|
|
<p>
|
|
This should give a response code of 200 and not redirect.
|
|
Now you can add the header to the command:
|
|
</p>
|
|
<pre>
|
|
curl --head http://example.com --header "Upgrade-Insecure-Requests: 1"</pre>
|
|
<p>
|
|
This should result in a 301 response with the
|
|
<code>Location</code> header set to the same URL but with
|
|
HTTPS. As a last check, you can test the HTTPS URL and make
|
|
sure it also returns 200.
|
|
</p>
|
|
|
|
<footer>
|
|
<p><a href="index.html">Back</a></p>
|
|
</footer>
|
|
</body>
|
|
</html>
|