Add upgrade insecure requests article
This commit is contained in:
commit
883734aa7e
81
static/assets/style.css
Normal file
81
static/assets/style.css
Normal file
@ -0,0 +1,81 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
time {
|
||||
color: #757575;
|
||||
}
|
||||
|
||||
.published {
|
||||
color: #757575;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.table-of-contents {
|
||||
display: inline-block;
|
||||
padding: 0 12px 8px 12px;
|
||||
border: 1px solid black;
|
||||
background-color: #F9F9F9;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
legend {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1565C0;
|
||||
}
|
||||
|
||||
h2 a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #EEE;
|
||||
border-radius: 4px;
|
||||
padding: 0 4px;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #EEE;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #151515;
|
||||
}
|
||||
|
||||
time, .published {
|
||||
color: #E0E0E0;
|
||||
}
|
||||
|
||||
.table-of-contents {
|
||||
background-color: #242424;
|
||||
border-color: #BDBDBD;
|
||||
}
|
||||
|
||||
legend {
|
||||
color: #BDBDBD;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #42A5F5;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
background-color: #323232;
|
||||
}
|
||||
}
|
||||
|
20
static/index.html
Normal file
20
static/index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Reimar's articles</title>
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Reimar's articles</h1>
|
||||
<p>This is where I publish my writings.</p>
|
||||
<br>
|
||||
<ul>
|
||||
<li>
|
||||
<time>2025-08-02</time> — <a href="upgrade-insecure-requests.html">Handling the Upgrade-Insecure-Requests header in nginx</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
166
static/upgrade-insecure-requests.html
Normal file
166
static/upgrade-insecure-requests.html
Normal file
@ -0,0 +1,166 @@
|
||||
<!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" />
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<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"><a href="#configuring">Configuring nginx</a></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"><a href="#cloudflare">Using Cloudflare</a></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 wanted 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"><a href="#testing">Testing the configuration</a></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>
|
||||
|
||||
<p><a href="index.html">Back</a></p>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user