1{
2 config,
3 lib,
4 inputs,
5 ...
6}:
7{
8 services.nginx = {
9 enable = true;
10 recommendedTlsSettings = true;
11 recommendedOptimisation = true;
12 recommendedGzipSettings = true;
13 recommendedProxySettings = true;
14 # /nginx_status
15 statusPage = true;
16 };
17
18 networking.firewall.allowedTCPPorts = [ 80 443 ];
19
20 # output json logs so we can consume them more easily
21 services.nginx.appendHttpConfig = ''
22 log_format json_logs escape=json '{'
23 '"_msg":"request completed",'
24 '"time":"$time_local",'
25 '"req.remoteAddr":"$remote_addr",'
26 '"req.method":"$request_method",'
27 '"req.url":"$uri",'
28 '"req.httpVersion":"$server_protocol",'
29 '"res.statusCode":$status,'
30 '"res.bodySize":$body_bytes_sent,'
31 '"req.headers.id":"$request_id",'
32 '"req.headers.referer":"$http_referer",'
33 '"req.headers.user-agent":"$http_user_agent",'
34 '"requestTime":$request_time'
35 '}';
36 access_log /var/log/nginx/access.log json_logs;
37 '';
38
39 users.users.nginx.extraGroups = [ "acme" ];
40
41 age.secrets.bunnyApiKey.file = ../../../secrets/bunnyApiKey.age;
42 security.acme = {
43 acceptTerms = true;
44 defaults = {
45 group = "nginx";
46 email = (import "${inputs.self}/personal.nix").emails.primary;
47 dnsProvider = "bunny";
48 credentialFiles = {
49 BUNNY_API_KEY_FILE = config.age.secrets.bunnyApiKey.path;
50 };
51 };
52 certs."poor.dog" = { };
53 certs."ptr.pet" = { };
54 certs."gaze.systems" = { };
55 };
56 services.nginx.virtualHosts."gaze.systems" = {
57 quic = true;
58 kTLS = true;
59 useACMEHost = "gaze.systems";
60 forceSSL = true;
61 };
62 services.nginx.virtualHosts."poor.dog" = {
63 quic = true;
64 kTLS = true;
65 useACMEHost = "poor.dog";
66 forceSSL = true;
67 };
68 services.nginx.virtualHosts."ptr.pet" = {
69 quic = true;
70 kTLS = true;
71 useACMEHost = "ptr.pet";
72 forceSSL = true;
73 };
74
75 services.fluent-bit.settings = {
76 parsers = [
77 {
78 name = "nginx_json";
79 format = "json";
80 time_key = "time";
81 time_format = "%d/%b/%Y:%H:%M:%S %z";
82 }
83 ];
84 pipeline = {
85 inputs = [
86 {
87 name = "nginx_metrics";
88 tag = "metrics.nginx";
89 status_url = "/nginx_status";
90 nginx_plus = false;
91 }
92 {
93 name = "tail";
94 tag = "logs.nginx";
95 path = "/var/log/nginx/*.log";
96 db = "/var/lib/fluent-bit/nginx-access.db";
97 "db.locking" = true;
98 buffer_chunk_size = "4m";
99 buffer_max_size = "32m";
100 parser = "nginx_json";
101 }
102 ];
103 filters = [
104 {
105 name = "modify";
106 match = "logs.nginx";
107 Add = [ "name nginx" ];
108 }
109 ];
110 };
111 };
112
113 # need so fluent-bit can access nginx
114 systemd.services.fluent-bit.serviceConfig.SupplementaryGroups = lib.mkForce "systemd-journal nginx";
115
116 services.vmalert.instances."".rules.groups = [
117 {
118 name = "nginx-logs";
119 type = "vlogs";
120 interval = "1m";
121 rules = [
122 {
123 record = "nginx_request_count";
124 expr = "name:nginx | stats (res.statusCode) count() as total_requests";
125 }
126 {
127 record = "nginx_request_latency";
128 # filter out subscribeRepos requests because they are long polling http L
129 expr = "name:nginx | filter req.url:!/xrpc/com.atproto.sync.subscribeRepos | stats avg(requestTime) avg, quantile(0.5, requestTime) p50, quantile(0.9, requestTime) p90, quantile(0.99, requestTime) p99";
130 }
131 ];
132 }
133 ];
134}