About HTTP header

When a browser opens a file or page on the web, the browser makes HTTP request as shown below and sends it to the Web server.

GET HTTP/2
scheme: https
host: alvine.org
filename: /

The web server responds to this by adding the response body (data of the page body) to the HTTP response header as shown below. It is case-insensitive.

HTTP/2 200 OK
alt-svc: h3=":443"; ma=2592000
cache-control: max-age=15768000
content-encoding: gzip
content-security-policy: default-src 'self'; frame-ancestors 'none';
content-type: text/html; charset=utf-8
cross-origin-embedder-policy: require-corp
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
etag: "d8uywr2ck2s594x-gzip"
last-modified: Tue, 01 Apr 2025 03:05:00 GMT
permissions-policy browsing-topics=(),interest-cohort=(),join-ad-interest-group=(),run-ad-auction=(),attribution-reporting=()
referrer-policy: strict-origin-when-cross-origin
server: Caddy
strict-transport-security: max-age=15768000;
vary: Accept-Encoding
x-content-type-options: nosniff
x-frame-options: DENY
content-length: 3442
date: Tue, 01 Apr 2025 12:34:19 GMT

The content of the response header can be modified by the web server administrator, and although many sites leave it at the default of the web server application, it can contain important information that is useful to the user. I would like to introduce what I consider to be particularly important.

Security

Cross-Origin- or something like that.

The word “security” sounds exaggerated, but it is all up to the browser to decide how to handle the response, so it is not a matter of setting this much and you are absolutely safe. It is necessary to understand that this is essentially just a “request” from the server.

Content-Security-Policy (CSP)

Content-Security-Policy: default-src 'self'; frame-ancestors 'none';

Header to restrict loading of third-party elements, useful for preventing XSS (cross-site scripting).

If default-src 'self' is set, elements not hosted on the same host as the page (web fonts, embedded SNS or share button, etc.) are blocked, as well as inline <style> and <script> elements. Most websites will be rendered useless, and you rarely see a site that uses only self.

Small personal web sites can easily implement it, but if they don’t do dynamic page generation and have no way to introduce XSS, like this site, it’s not worth it.

The well-known x-frame-options: DENY, which prevents clickjacking, has already been deprecated and its function has been replaced by frame-ancestors ‘none’.

CSP is a very deep feature, but it is far beyond my skill. For now, it might be a good idea to set default-src.

Cross-Origin-Resource-Policy (CORP)

Cross-Origin-Resource-Policy: same-origin

Restrict loading of this site’s resources from another origin (another site). The default is cross-origin, which allows requests from any origin to load resources. If it is set to same-origin or same-site, then resources cannot be loaded directly from other sites. However, since blocking loading is a browser-side function, it does not necessarily mean that the response body will not be sent.

Cross-Origin-Embedder-Policy (COEP)

Cross-Origin-Embedder-Policy: require-corp

Restrict resources other than the specified origin from being loaded on this site. The default is unsafe-none, in which case cross-origin resources can be loaded without explicit permission in CORP. If set to require-corp, it will block reading of any origin other than the one explicitly specified in CORP.

Cross-Origin-Opener-Policy (COOP)

Cross-Origin-Opener-Policy: same-origin

Restrict the site from sharing a browsing context with another origin. The default is unsafe-none, which means that when another origin is opened from this site, the opened origin can control the parent window using JavaScript’s window.opener. If same-origin is used, the browsing context is not shared between origins, so they cannot communicate with each other. It is better to set same-origin when there is no need to be controlled by other sites.

same-origin-allow-popups is used for kind of a payment required services.

Strict-Transport-Security (HSTS)

Strict-Transport-Security: max-age=15768000;

Forces the browser to redirect to HTTPS when it sends request over HTTP. The redirection to HTTPS for this site will be in effect for the specified number of seconds. Common examples are max-age=15768000 for half a year and max-age=31536000 for one year. However, it is meaningless if it is not cached in the browser. So no matter how long max-age is set, it is meaningless for the first access or access in secret mode.

X-Content-Type-Options

X-Content-Type-Options nosniff

Prevent the browser from sniffing the MIME type of a file.

Headers beginning with X- are either private, non-standardized headers, or were once non-standardized but are now standardized. The latter is the case. To begin with, starting a private header with X- has already been deprecated1.

Privacy

Permissions-Policy

Permissions-Policy: browsing-topics=(), interest-cohort=(), join-ad-interest-group=(), run-ad-auction=(), attribution-reporting=()

This is the core of the “kind HTTP header” that I wanted to convey in this article.

Permissions-Policy is a header that restricts the use of location information and cameras from the website side, but this is not included in this article because the default browser setting should ask for permission and such a setting is not used for personal sites. There is a more important setting than that. Opting-out of Google’s privacy sandbox.

Privacy Sandbox

Privacy Snadbox is the technology to track users “on the device side, not on the server side” by Google, which has finally been closed to the use of third-party cookies as a result of legal restrictions. The name “sandbox” seems to have been given to this technology, which is currently under development.

All of this can only be described as EVIL. Even if your site is being served by Google’s ads, you should disable them.

There is a way to opt out through browser settings, but they no longer make any attempt to hide their intent to deceive those with little concern for security or privacy. Some features can be disabled (at least for this site) via server-side response headers, so let’s disable them for the good of all humankinnd.

Topics API

Permissions-Policy: browsing-topics=()

The Topics API is a means of tracking users across sites, replacing third-party cookies. Google got flammed worldwidely when it announced FLoC (Federated Learning of Cohorts), a system for grouping users by preferences, and the Topics API seems to be the successor of FLoC. It allows browsers to list and store categories of interest based on the user’s browsing history, and allows the top five categories to be browsed upon request from the website side. However, there is a 5% chance that a random category will be returned.

You can disable the viewer’s browsers to analize contents (at least on your site) by setting browsing-topics=()2.

FLoC

Permissions-Policy: interest-cohort=()

interest-cohort is FLoC as explained above. It has been succeeded to the Topics API yet you can opt it out with interest-cohort=() just in case.

Protected Audience API

Permissions-Policy: join-ad-interest-group=(), run-ad-auction=()

The Protected Audience API is a system that divides users with common interests into the Interest Groups in advance and run auctions for advertising on their devices. It was originally developed under the name FLEDGE (First “Locally-Executed Decision over Groups” Experiment), but it was renamed “Protected Audience” in 20233.

The target audience is “users who have visited a company’s website in the past,” and the purpose is to give the company an opportunity to remarket to these users.

You can disable adding browsers to interest groups with join-ad-interest-group=(). You can disable the ability to run auctions with run-ad-auction=(), but this may not be relevant if you are not loading ad scripts. However, it was not clear to me what data is used to add the ads to the interest group. (This seems to be similar to the Topics API, but unclear.)

Attribution Reporting API

Permissions-Policy: attribution-reporting=()

The Attribution Reporting API is a system that allows browsers to send the results of measurement of “when an ad view led to a purchase” to the advertising platform later. It seems completely irrelevant to sites that do not use any ads, such as this site. Although Google is implying that they are going to use for purposes other than advertising in the future4, so it may be a good idea to disable it from now5.

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Restrict the referer sent in the request.

Basically, at least between the same origin, sending full URL referer makes it easy to inspect logs, and it is acceptable to send only the domain name to external origins. However, you do not want to send anything from an HTTPS origin to an HTTP origin at all.

In such a case, setting no-referrer is not proper because it makes it difficult to inspect requests even between the same origin. The best choice is strict-origin-when-cross-origin. This is the default setting for almost all browsers, including Chrome and Firefox.

Reducing network traffic

Cache-Control

Cache-Control: max-age=15768000

Browsers do not send all HTTP requests, but may create responses from caches stored in themselves. By using max-age to inform browsers how long the response can be reused, it is possible to reduce the extra traffic load on the client.

There is no need to worry that users will not be notified of site updates even if max-age is set to a longer value. The browser judges whether the response body it receives is the same as the one in its cache based on the values of last-modified and etag in the response header, and returns the status code 304 Not Modified if they are the same, and creates a response body from its cache. If so, it returns the status code 304 Not Modified and creates a response body from the cache. If you find many 304s in the server logs, you should set a longer cache period.

In addition, modern browsers are smart enough to think that a site that has not been updated in a year is unlikely to be updated again, so they sometimes reuse caches that are more than a year old from last-modified and have long passed their max-age. This is called a “heuristic cache”.

Troubleshooting

Now, I have introduced a fairly strict configuration method, but strict settings sometimes impair convenience.

I would like to show some actual problems caused by HTTP header settings.

SVG turns to be a black

This is the favicon of this site. You’ve seen it right?

One day a while after setting CSP. I suddenly wondered, “Is the favicon displayed properly?” and I opened them in my browser.

All of them were displayed correctly but only favicon.svg was displayed as a black square.

At first I thought that the dark mode of the browser might be doing something wrong, but there was no probrem with that. I finally found the cause by downloading and opening the file with a text editor.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
    version="1.1"
    id="svg1"
    width="256.00003"
    height="256.00003"
    viewBox="0 0 256.00003 256.00003"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:svg="http://www.w3.org/2000/svg">
    <style id="style1">
        path {
            fill: #000;
        }
        @media (prefers-color-scheme: dark) {
            path {
                fill: #fff;
            }
        }
    </style>
    <defs id="defs1" />
    <rect
        style=""
        id="rect1"
        width="256"
        height="256"
        x="0"
        y="0" />
    <path
        style=""
        d="M 0,127.99999 (...) "
        id="path3" />
</svg>

The <rect> and <path> styles are completely missing. (I wonder why style1 is still there. It doesn’t change color even in dark mode, so I actually don’t need it.)

I solved the problem by returning Content-Security-Policy: style-src 'unsafe-inline'; only for requests for image/svg+xml.

base64 image does not show up

One more thing related to CSP. There’s a base64-encoded image on some page on this site, but it was not shown with the CSP set to default-src: 'self'. To display it, I had to specify the scheme by adding img-src 'self' data:;

Twitter OGP

In this site, illustration pages are set up with metadata for OGP (Open Graph Protocol), to share on SNS (especially Twitter). It makes possible to “embedding the webpage in Twitter”.

An example of OGP image

Example of OGP

However, for this site, there was a problem that only the thumbnail images were not loaded even though the metadata of the articles were loaded.

The official OGP Validator was ended, so checking at the post page.

The thumbnails on this site are 160x160, which is much smaller than the usual OGP images (often 1200x630), so I thought that it might be running afoul of the minimum size, but could not solve it6. I left it for a while because it was troublesome, but then I noticed that it could be a CSP problem, and tried setting Cross-Origin-Resource-Policy: cross-origin to the directory for thumbnails, and they were displayed.

Thumbnail is shown properly

Websites I reffered

Specific sources are noted in footnotes.


  1. RFC 6648 - Deprecating the “X-” Prefix and Similar Constructs in Application Protocols↩︎

  2. You can opt out of topic calculation for specific pages on your site by including the Permissions-Policy: browsing-topics=() Permissions-Policy header on a page to prevent topics calculation for all users on that page only. Subsequent visits to other pages on your site won’t be affected: if you set a policy to block the Topics API on one page, this won’t affect other pages.

    Topics API customization and opt-out  |  Privacy Sandbox↩︎

  3. Protected Audience API: Our new name for FLEDGE↩︎

  4. Note: In the future, the Attribution Reporting API may serve use cases that are not related to advertising.

    Attribution Reporting for Web overview  |  Privacy Sandbox↩︎

  5. A site can disable the Attribution Reporting API for all parties, including scripts with top-level access, by sending the HTTP response header: Permissions-Policy: attribution-reporting=()

    Attribution Reporting for Web overview  |  Privacy Sandbox↩︎

  6. I couldn’t find any limitations on the size of OGP image on the Twitter’s document. | Cards markup | Docs | Twitter Developer Platform↩︎