Repository navigation
Expand file tree
/
Copy pathnginx.conf
More file actions
120 lines (109 loc) · 5.22 KB
/
Copy pathnginx.conf
File metadata and controls
120 lines (109 loc) · 5.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# Static server for the tui-tools site on Quave ONE.
#
# This file is copied into the image as an nginx *template*
# (/etc/nginx/templates/default.conf.template). The nginx entrypoint runs
# envsubst over it at container start, so ${PORT} becomes whatever port the app
# environment is set to — Quave ONE's default is 3000 and that is the image's
# default too. NGINX_ENVSUBST_FILTER in the Dockerfile keeps the substitution to
# PORT alone, so nginx's own $uri and friends are left untouched.
#
# security-headers.conf is included in every block that sets a header of its
# own; see that file for why.
#
# On location precedence: nginx picks a regex location over a prefix one, which
# is why /_astro/ is marked `^~` — without it a fingerprinted .svg would fall
# into the image rule below and get a day instead of a year.
# One canonical host. `www` is registered on the environment and holds a
# certificate of its own, so it is reachable and has to be answered — it just is
# not answered with the site. A 301 here rather than a canonical link alone,
# because two hosts serving identical HTML is a duplicate the crawler has to
# resolve on its own, and a redirect resolves it first.
#
# This block is matched by name, so it has to come before the default one below;
# the ingress terminates TLS, which is why the redirect names https:// itself
# rather than reading a scheme it cannot see. $request_uri survives envsubst
# because NGINX_ENVSUBST_FILTER is limited to PORT.
server {
listen ${PORT};
server_name www.tui.tools;
include /etc/nginx/snippets/security-headers.conf;
return 301 https://tui.tools$request_uri;
}
# The default server, and the one the platform's health check reaches: a probe
# arrives by pod IP with no Host header that matches anything above.
server {
listen ${PORT} default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
# The redirects nginx generates itself — the trailing slash try_files adds
# for a directory, above all — are relative. Left absolute, nginx rebuilds
# them from the scheme and port it can see, which behind the ingress means
# http:// and ${PORT}: `curl -sI https://tui.tools/security` answered
# `Location: http://tui.tools:3000/security/`, a downgrade to plain HTTP that
# also published the container's port. A relative Location has neither, and
# the client keeps the scheme and host it already used. The explicit
# `return 301 https://tui.tools$request_uri` for www is unaffected: a return
# with a full URL is sent as written.
absolute_redirect off;
include /etc/nginx/snippets/security-headers.conf;
# Lightweight health endpoint for the platform check.
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
# Astro fingerprints everything under /_astro/, so those files are immutable:
# a change to one changes its name.
location ^~ /_astro/ {
include /etc/nginx/snippets/security-headers.conf;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
# The machine-readable catalog. A public, read-only document other programs
# fetch — the launcher, and anything else that wants the family's list — so
# it is the one route that answers cross-origin. Ten minutes of cache against
# an hourly rebuild: a poller costs the origin nothing, and a new release is
# visible within the same hour it would have been anyway.
location = /catalog.json {
include /etc/nginx/snippets/security-headers.conf;
# Content-Type comes from mime.types by extension — application/json, which
# is UTF-8 by definition — and not from an add_header, which would append a
# second one rather than replace it.
add_header Access-Control-Allow-Origin "*" always;
add_header Cache-Control "public, max-age=600" always;
}
# SEO files always fresh.
location ~* ^/(robots\.txt|llms\.txt|sitemap-index\.xml|sitemap-\d+\.xml)$ {
include /etc/nginx/snippets/security-headers.conf;
add_header Cache-Control "no-cache" always;
}
# Everything else the site serves as an image — the brand SVGs and the icons
# and screenshots the catalog downloads — is not fingerprinted and is replaced
# in place on the next build, so it gets a day rather than a year.
location ~* \.(svg|png|jpg|jpeg|webp|gif|ico|woff2?)$ {
include /etc/nginx/snippets/security-headers.conf;
add_header Cache-Control "public, max-age=86400" always;
}
# The pages. A multi-page static site, not an SPA: an unknown path is a 404,
# not the home page with a 200. Pages are directories (trailingSlash:
# "always"), so $uri/ is what resolves them.
#
# HTML is the part that changes every build — a new release, a new tool — and
# the build runs hourly, so revalidating costs one conditional request.
location / {
include /etc/nginx/snippets/security-headers.conf;
add_header Cache-Control "no-cache" always;
try_files $uri $uri/ =404;
}
error_page 404 /404.html;
location = /404.html {
internal;
include /etc/nginx/snippets/security-headers.conf;
add_header Cache-Control "no-cache" always;
}
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_vary on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml application/xml;
}