| | @@ -4,20 +4,19 @@ |
| 4 | 4 | instructions][scgii], which may suffice for your purposes if your needs |
| 5 | 5 | are simple. |
| 6 | 6 | |
| 7 | 7 | Here, we add more detailed information on nginx itself, plus details |
| 8 | 8 | about running it on Debian type OSes. We focus on Debian 10 (Buster) and |
| 9 | | -Ubuntu 18.04 here, which are common Tier 1 OS offerings for [virtual |
| 10 | | -private servers][vps]. This material may not work for older OSes. It is |
| 9 | +Ubuntu 20.04 here, which are common Tier 1 OS offerings for [virtual |
| 10 | +private servers][vps] at the time of writing. This material may not work for older OSes. It is |
| 11 | 11 | known in particular to not work as given for Debian 9 and older! |
| 12 | 12 | |
| 13 | | -If you want to add TLS to this configuration, that is covered [in a |
| 14 | | -separate document][tls] which was written with the assumption that |
| 15 | | -you’ve read this first. |
| 13 | +We also cover adding TLS to the basic configuration, because several |
| 14 | +details depend on the host OS and web stack details. Besides, TLS is |
| 15 | +widely considered part of the baseline configuration these days. |
| 16 | 16 | |
| 17 | 17 | [scgii]: ../any/scgi.md |
| 18 | | -[tls]: ../../tls-nginx.md |
| 19 | 18 | [vps]: https://en.wikipedia.org/wiki/Virtual_private_server |
| 20 | 19 | |
| 21 | 20 | |
| 22 | 21 | ## <a name="benefits"></a>Benefits |
| 23 | 22 | |
| | @@ -88,11 +87,11 @@ |
| 88 | 87 | |
| 89 | 88 | * **SCGI** — The [SCGI protocol][scgip] provides the simplicity of CGI |
| 90 | 89 | without its performance problems. |
| 91 | 90 | |
| 92 | 91 | * **SSH** — This method exists primarily to avoid the need for HTTPS, |
| 93 | | - but we *want* HTTPS. (We’ll get to that in [another document][tls].) |
| 92 | + but we *want* HTTPS. (We’ll get to that [below](#tls).) |
| 94 | 93 | There is probably a way to get nginx to proxy Fossil to HTTPS via |
| 95 | 94 | SSH, but it would be pointlessly complicated. |
| 96 | 95 | |
| 97 | 96 | SCGI it is, then. |
| 98 | 97 | |
| | @@ -190,20 +189,20 @@ |
| 190 | 189 | repetition across `server { }` blocks when setting up multiple domains |
| 191 | 190 | on a single server. |
| 192 | 191 | |
| 193 | 192 | The configuration for `foo.net` is similar. |
| 194 | 193 | |
| 195 | | -See [the nginx docs](http://nginx.org/en/docs/) for more ideas. |
| 194 | +See [the nginx docs](https://nginx.org/en/docs/) for more ideas. |
| 196 | 195 | |
| 197 | 196 | |
| 198 | 197 | ## <a name="http"></a>Proxying HTTP Anyway |
| 199 | 198 | |
| 200 | 199 | [Above](#modes), we argued that proxying SCGI is a better option than |
| 201 | 200 | making nginx reinterpret Fossil’s own implementation of HTTP. If you |
| 202 | 201 | want Fossil to speak HTTP, just [set Fossil up as a standalone |
| 203 | 202 | server](../any/none.md). And if you want nginx to [provide TLS |
| 204 | | -encryption for Fossil][tls], proxying HTTP instead of SCGI provides no |
| 203 | +encryption for Fossil](#tls), proxying HTTP instead of SCGI provides no |
| 205 | 204 | benefit. |
| 206 | 205 | |
| 207 | 206 | However, it is still worth showing the proper method of proxying |
| 208 | 207 | Fossil’s HTTP server through nginx if only to make reading nginx |
| 209 | 208 | documentation on other sites easier: |
| | @@ -216,6 +215,390 @@ |
| 216 | 215 | The most common thing people get wrong when hand-rolling a configuration |
| 217 | 216 | like this is to get the slashes wrong. Fossil is senstitive to this. For |
| 218 | 217 | instance, Fossil will not collapse double slashes down to a single |
| 219 | 218 | slash, as some other HTTP servers will. |
| 220 | 219 | |
| 220 | + |
| 221 | +## <a name="tls"></a> Adding TLS (HTTPS) Support |
| 222 | + |
| 223 | +One of the [many ways](../../ssl.wiki) to provide TLS-encrypted HTTP access |
| 224 | +(a.k.a. HTTPS) to Fossil is to run it behind a web proxy that supports |
| 225 | +TLS. One such option is nginx on Debian, so we show the details of that |
| 226 | +here. |
| 227 | + |
| 228 | +You can extend this guide to other operating systems by following the |
| 229 | +instructions found via [the front Certbot web page][cb] instead, telling |
| 230 | +it what OS and web stack you’re using. Chances are good that they’ve got |
| 231 | +a good guide for you already. |
| 232 | + |
| 233 | + |
| 234 | +### <a id="leew"></a> Configuring Let’s Encrypt, the Easy Way |
| 235 | + |
| 236 | +If your web serving needs are simple, [Certbot][cb] can configure nginx |
| 237 | +for you and keep its certificates up to date. Simply follow Certbot’s |
| 238 | +[nginx on Ubuntu 20.04 LTS guide][cbnu]. |
| 239 | + |
| 240 | +Unfortunately, the setup above was beyond Certbot’s ability to cope the |
| 241 | +last time we tried it. The use of per-subdomain files in particular |
| 242 | +confused Certbot, so we had to [arrange these details manually](#lehw), |
| 243 | +else the Let’s Encrypt [ACME] exchange failed in the necessary domain |
| 244 | +validation steps. |
| 245 | + |
| 246 | +At this point, if your configuration needs are simple, needing only a |
| 247 | +single Internet domain and a single Fossil repo, you might wish to try |
| 248 | +to reduce the above configuration to a more typical single-file nginx |
| 249 | +config, which Certbot might then cope with out of the box. |
| 250 | + |
| 251 | + |
| 252 | + |
| 253 | +### <a id="lehw"></a> Configuring Let’s Encrypt, the Hard Way |
| 254 | + |
| 255 | +The primary motivation for this section is that it documents the manual |
| 256 | +Certbot configuration on my public Fossil-based site. I’m addressing |
| 257 | +the “me” years hence who needs to upgrade to Ubuntu 22.04 or 24.04 LTS |
| 258 | +and has forgotten all of this stuff. 😉 |
| 259 | + |
| 260 | + |
| 261 | +#### Step 1: Shifting into Manual |
| 262 | + |
| 263 | +The first thing we’ll do is install Certbot in the normal way, but we’ll |
| 264 | +turn off all of the Certbot automation and won’t follow through with use |
| 265 | +of the `--nginx` plugin: |
| 266 | + |
| 267 | + $ sudo snap install --classic certbot |
| 268 | + $ sudo systemctl disable certbot.timer |
| 269 | + |
| 270 | +Next, edit `/etc/letsencrypt/renewal/example.com.conf` to disable the |
| 271 | +nginx plugins. You’re looking for two lines setting the “install” and |
| 272 | +“auth” plugins to “nginx”. You can comment them out or remove them |
| 273 | +entirely. |
| 274 | + |
| 275 | + |
| 276 | +#### Step 2: Configuring nginx |
| 277 | + |
| 278 | +This is a straightforward extension to the HTTP-only configuration |
| 279 | +[above](#config): |
| 280 | + |
| 281 | + server { |
| 282 | + server_name .foo.net; |
| 283 | + |
| 284 | + include local/tls-common; |
| 285 | + |
| 286 | + charset utf-8; |
| 287 | + |
| 288 | + access_log /var/log/nginx/foo.net-https-access.log; |
| 289 | + error_log /var/log/nginx/foo.net-https-error.log; |
| 290 | + |
| 291 | + # Bypass Fossil for the static Doxygen docs |
| 292 | + location /doc/html { |
| 293 | + root /var/www/foo.net; |
| 294 | + |
| 295 | + location ~* \.(html|ico|css|js|gif|jpg|png)$ { |
| 296 | + expires 7d; |
| 297 | + add_header Vary Accept-Encoding; |
| 298 | + access_log off; |
| 299 | + } |
| 300 | + } |
| 301 | + |
| 302 | + # Redirect everything else to the Fossil instance |
| 303 | + location / { |
| 304 | + include scgi_params; |
| 305 | + scgi_pass 127.0.0.1:12345; |
| 306 | + scgi_param HTTPS "on"; |
| 307 | + scgi_param SCRIPT_NAME ""; |
| 308 | + } |
| 309 | + } |
| 310 | + server { |
| 311 | + server_name .foo.net; |
| 312 | + root /var/www/foo.net; |
| 313 | + include local/http-certbot-only; |
| 314 | + access_log /var/log/nginx/foo.net-http-access.log; |
| 315 | + error_log /var/log/nginx/foo.net-http-error.log; |
| 316 | + } |
| 317 | + |
| 318 | +One big difference between this and the HTTP-only case is |
| 319 | +that we need two `server { }` blocks: one for HTTPS service, and |
| 320 | +one for HTTP-only service. |
| 321 | + |
| 322 | + |
| 323 | +##### HTTP over TLS (HTTPS) Service |
| 324 | + |
| 325 | +The first `server { }` block includes this file, `local/tls-common`: |
| 326 | + |
| 327 | + listen 443 ssl; |
| 328 | + |
| 329 | + ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; |
| 330 | + ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; |
| 331 | + |
| 332 | + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; |
| 333 | + |
| 334 | + ssl_stapling on; |
| 335 | + ssl_stapling_verify on; |
| 336 | + |
| 337 | + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; |
| 338 | + ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-CBC-SHA:ECDHE-ECDSA-AES256-CBC-SHA:ECDHE-ECDSA-AES128-CBC-SHA256:ECDHE-ECDSA-AES256-CBC-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-CBC-SHA:ECDHE-RSA-AES256-CBC-SHA:ECDHE-RSA-AES128-CBC-SHA256:ECDHE-RSA-AES256-CBC-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-CBC-SHA:DHE-RSA-AES256-CBC-SHA:DHE-RSA-AES128-CBC-SHA256:DHE-RSA-AES256-CBC-SHA256"; |
| 339 | + ssl_session_cache shared:le_nginx_SSL:1m; |
| 340 | + ssl_prefer_server_ciphers on; |
| 341 | + ssl_session_timeout 1440m; |
| 342 | + |
| 343 | +These are the common TLS configuration parameters used by all domains |
| 344 | +hosted by this server. |
| 345 | + |
| 346 | +The first line tells nginx to accept TLS-encrypted HTTP connections on |
| 347 | +the standard HTTPS port. It is the same as `listen 443; ssl on;` in |
| 348 | +older versions of nginx. |
| 349 | + |
| 350 | +Since all of those domains share a single TLS certificate, we reference |
| 351 | +the same `example.com/*.pem` files written out by Certbot with the |
| 352 | +`ssl_certificate*` lines. |
| 353 | + |
| 354 | +The `ssl_dhparam` directive isn’t strictly required, but without it, the |
| 355 | +server becomes vulnerable to the [Logjam attack][lja] because some of |
| 356 | +the cryptography steps are precomputed, making the attacker’s job much |
| 357 | +easier. The parameter file this directive references should be |
| 358 | +generated automatically by the Let’s Encrypt package upon installation, |
| 359 | +making those parameters unique to your server and thus unguessable. If |
| 360 | +the file doesn’t exist on your system, you can create it manually, so: |
| 361 | + |
| 362 | + $ sudo openssl dhparam -out /etc/letsencrypt/dhparams.pem 2048 |
| 363 | + |
| 364 | +Beware, this can take a long time. On a shared Linux host I tried it on |
| 365 | +running OpenSSL 1.1.0g, it took about 21 seconds, but on a fast, idle |
| 366 | +iMac running LibreSSL 2.6.5, it took 8 minutes and 4 seconds! |
| 367 | + |
| 368 | +The next section is also optional. It enables [OCSP stapling][ocsp], a |
| 369 | +protocol that improves the speed and security of the TLS connection |
| 370 | +negotiation. |
| 371 | + |
| 372 | +The next section containing the `ssl_protocols` and `ssl_ciphers` lines |
| 373 | +restricts the TLS implementation to only those protocols and ciphers |
| 374 | +that are currently believed to be safe and secure. This section is the |
| 375 | +one most prone to bit-rot: as new attacks on TLS and its associated |
| 376 | +technologies are discovered, this configuration is likely to need to |
| 377 | +change. Even if we fully succeed in keeping this document up-to-date in |
| 378 | +the face of the evolving security landscape, we’re recommending static |
| 379 | +configurations for your server: it will thus be up to you to track |
| 380 | +changes in this document and others to merge the changes into your local |
| 381 | +static configuration. |
| 382 | + |
| 383 | +Running a TLS certificate checker against your site occasionally is a |
| 384 | +good idea. The most thorough service I’m aware of is the [Qualys SSL |
| 385 | +Labs Test][qslt], which gives the site I’m basing this guide on an “A+” |
| 386 | +rating at the time of this writing. The long `ssl_ciphers` line above is |
| 387 | +based on [their advice][qslc]: the default nginx configuration tells |
| 388 | +OpenSSL to use whatever ciphersuites it considers “high security,” but |
| 389 | +some of those have come to be considered “weak” in the time between that |
| 390 | +judgement and the time of this writing. By explicitly giving the list of |
| 391 | +ciphersuites we want OpenSSL to use within nginx, we can remove those |
| 392 | +that become considered weak in the future. |
| 393 | + |
| 394 | +<a id=”hsts”></a>There are a few things you can do to get an even better |
| 395 | +grade, such as to enable [HSTS][hsts]: |
| 396 | + |
| 397 | + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; |
| 398 | + |
| 399 | +This prevents a particular variety of [man in the middle attack][mitm] |
| 400 | +where our HTTP-to-HTTPS permanent redirect is intercepted, allowing the |
| 401 | +attacker to prevent the automatic upgrade of the connection to a secure |
| 402 | +TLS-encrypted one. I didn’t enable that in the configuration above |
| 403 | +because it is something a site administrator should enable only after |
| 404 | +the configuration is tested and stable, and then only after due |
| 405 | +consideration. There are ways to lock your users out of your site by |
| 406 | +jumping to HSTS hastily. When you’re ready, there are [guides you can |
| 407 | +follow][nest] elsewhere online. |
| 408 | + |
| 409 | + |
| 410 | +##### HTTP-Only Service |
| 411 | + |
| 412 | +While we’d prefer not to offer HTTP service at all, we need to do so for |
| 413 | +two reasons: |
| 414 | + |
| 415 | +* The temporary reason is that until we get Let’s Encrypt certificates |
| 416 | + minted and configured properly, we can’t use HTTPS yet at all. |
| 417 | + |
| 418 | +* The ongoing reason is that the Certbot [ACME][acme] HTTP-01 |
| 419 | + challenge used by the Let’s Encrypt service only runs over HTTP. This is |
| 420 | + not only because it has to work before HTTPS is first configured, |
| 421 | + but also because it might need to work after a certificate is |
| 422 | + accidentally allowed to lapse to get that server back into a state |
| 423 | + where it can speak HTTPS safely again. |
| 424 | + |
| 425 | +So, from the second `service { }` block, we include this file to set up |
| 426 | +the minimal HTTP service we require, `local/http-certbot-only`: |
| 427 | + |
| 428 | + listen 80; |
| 429 | + listen [::]:80; |
| 430 | + |
| 431 | + # This is expressed as a rewrite rule instead of an "if" because |
| 432 | + # http://wiki.nginx.org/IfIsEvil |
| 433 | + #rewrite ^(/.well-known/acme-challenge/.*) $1 break; |
| 434 | + |
| 435 | + # Force everything else to HTTPS with a permanent redirect. |
| 436 | + #return 301 https://$host$request_uri; |
| 437 | + |
| 438 | +As written above, this configuration does nothing other than to tell |
| 439 | +nginx that it’s allowed to serve content via HTTP on port 80 as well. |
| 440 | +We’ll uncomment the `rewrite` and `return` directives below, when we’re |
| 441 | +ready to begin testing. |
| 442 | + |
| 443 | +Notice that most of the nginx directives given [above](#config) moved up |
| 444 | +into the TLS `server { }` block, because we eventually want this site to |
| 445 | +be as close to HTTPS-only as we can get it. |
| 446 | + |
| 447 | + |
| 448 | +#### Step 3: Dry Run |
| 449 | + |
| 450 | +We want to first request a dry run, because Let’s Encrypt puts some |
| 451 | +rather low limits on how often you’re allowed to request an actual |
| 452 | +certificate. You want to be sure everything’s working before you do |
| 453 | +that. You’ll run a command something like this: |
| 454 | + |
| 455 | + $ sudo certbot certonly --webroot --dry-run \ |
| 456 | + --webroot-path /var/www/example.com \ |
| 457 | + -d example.com -d www.example.com \ |
| 458 | + -d example.net -d www.example.net \ |
| 459 | + --webroot-path /var/www/foo.net \ |
| 460 | + -d foo.net -d www.foo.net |
| 461 | + |
| 462 | +There are two key options here. |
| 463 | + |
| 464 | +First, we’re telling Certbot to use its `--webroot` plugin instead of |
| 465 | +the automated `--nginx` plugin. With this plugin, Certbot writes the |
| 466 | +[ACME][acme] HTTP-01 challenge files to the static web document root |
| 467 | +directory behind each domain. For this example, we’ve got two web |
| 468 | +roots, one of which holds documents for two different second-level |
| 469 | +domains (`example.com` and `example.net`) with `www` at the third level |
| 470 | +being optional. This is a common sort of configuration these days, but |
| 471 | +you needn’t feel that you must slavishly imitate it. The other web root |
| 472 | +is for an entirely different domain, also with `www` being optional. |
| 473 | +Since all of these domains are served by a single nginx instance, we |
| 474 | +need to give all of this in a single command, because we want to mint a |
| 475 | +single certificate that authenticates all of these domains. |
| 476 | + |
| 477 | +The second key option is `--dry-run`, which tells Certbot not to do |
| 478 | +anything permanent. We’re just seeing if everything works as expected, |
| 479 | +at this point. |
| 480 | + |
| 481 | + |
| 482 | +##### Troubleshooting the Dry Run |
| 483 | + |
| 484 | +If that didn’t work, try creating a manual test: |
| 485 | + |
| 486 | + $ mkdir -p /var/www/example.com/.well-known/acme-challenge |
| 487 | + $ echo hi > /var/www/example.com/.well-known/acme-challenge/test |
| 488 | + |
| 489 | +Then try to pull that file over HTTP — not HTTPS! — as |
| 490 | +`http://example.com/.well-known/acme-challenge/test`. I’ve found that |
| 491 | +using Firefox or Safari is better for this sort of thing than Chrome, |
| 492 | +because Chrome is more aggressive about automatically forwarding URLs to |
| 493 | +HTTPS even if you requested “`http`”. |
| 494 | + |
| 495 | +In extremis, you can do the test manually: |
| 496 | + |
| 497 | + $ curl -i http://example.com/.well-known/acme-challenge/test |
| 498 | + HTTP/1.1 200 OK |
| 499 | + Server: nginx/1.14.0 (Ubuntu) |
| 500 | + Date: Sat, 19 Jan 2019 19:43:58 GMT |
| 501 | + Content-Type: application/octet-stream |
| 502 | + Content-Length: 3 |
| 503 | + Last-Modified: Sat, 19 Jan 2019 18:21:54 GMT |
| 504 | + Connection: keep-alive |
| 505 | + ETag: "5c436ac2-4" |
| 506 | + Accept-Ranges: bytes |
| 507 | + |
| 508 | + hi |
| 509 | + |
| 510 | +The key bits you’re looking for here are the “200 OK” response code at |
| 511 | +the start and the “hi” line at the end. (Or whatever you wrote in to the |
| 512 | +test file.) |
| 513 | + |
| 514 | +If you get a 301 redirect to an `https://` URI, you either haven’t |
| 515 | +uncommented the `rewrite` line for HTTP-only service for this directory, |
| 516 | +or there’s some other problem with the “redirect to HTTPS” config. |
| 517 | + |
| 518 | +If you get a 404 or other error response, you need to look into your web |
| 519 | +server logs to find out what’s going wrong. |
| 520 | + |
| 521 | +If you’re still running into trouble, the log file written by Certbot |
| 522 | +can be helpful. It tells you where it’s writing the ACME files early in |
| 523 | +each run. |
| 524 | + |
| 525 | + |
| 526 | + |
| 527 | +#### Step 4: Getting Your First Certificate |
| 528 | + |
| 529 | +Once the dry run is working, you can drop the `--dry-run` option and |
| 530 | +re-run the long command above. (The one with all the `--webroot*` |
| 531 | +flags.) This should now succeed, and it will save all of those flag |
| 532 | +values to your Let’s Encrypt configuration file, so you don’t need to |
| 533 | +keep giving them. |
| 534 | + |
| 535 | + |
| 536 | + |
| 537 | +#### Step 5: Test It |
| 538 | + |
| 539 | +Edit the `local/http-certbot-only` file and uncomment the `redirect` and |
| 540 | +`return` directives, then restart your nginx server and make sure it now |
| 541 | +forces everything to HTTPS like it should: |
| 542 | + |
| 543 | + $ sudo systemctl restart nginx |
| 544 | + |
| 545 | +Test ideas: |
| 546 | + |
| 547 | +* Visit both Fossil and non-Fossil URLs |
| 548 | + |
| 549 | +* Log into the repo, log out, and log back in |
| 550 | + |
| 551 | +* Clone via `http`: ensure that it redirects to `https`, and that |
| 552 | + subsequent `fossil sync` commands go directly to `https` due to the |
| 553 | + 301 permanent redirect. |
| 554 | + |
| 555 | +This forced redirect is why we don’t need the Fossil Admin → Access |
| 556 | +"Redirect to HTTPS on the Login page" setting to be enabled. Not only |
| 557 | +is it unnecessary with this HTTPS redirect at the front-end proxy level, |
| 558 | +it would actually [cause an infinite redirect loop if |
| 559 | +enabled](./ssl.wiki#rloop). |
| 560 | + |
| 561 | + |
| 562 | + |
| 563 | +#### Step 6: Switch to HTTPS Sync |
| 564 | + |
| 565 | +Fossil remembers permanent HTTP-to-HTTPS redirects on sync since version |
| 566 | +2.9, so all you need to do to switch your syncs to HTTPS is: |
| 567 | + |
| 568 | + $ fossil sync -R /path/to/repo.fossil |
| 569 | + |
| 570 | + |
| 571 | +#### Step 7: Renewing Automatically |
| 572 | + |
| 573 | +Now that the configuration is solid, you can renew the LE cert with the |
| 574 | +`certbot` command from above without the `--dry-run` flag plus a restart |
| 575 | +of nginx: |
| 576 | + |
| 577 | + sudo certbot certonly --webroot \ |
| 578 | + --webroot-path /var/www/example.com \ |
| 579 | + -d example.com -d www.example.com \ |
| 580 | + -d example.net -d www.example.net \ |
| 581 | + --webroot-path /var/www/foo.net \ |
| 582 | + -d foo.net -d www.foo.net |
| 583 | + sudo systemctl restart nginx |
| 584 | + |
| 585 | +I put those commands in a script in the `PATH`, then arrange to call that |
| 586 | +periodically. Let’s Encrypt doesn’t let you renew the certificate very |
| 587 | +often unless forced, and when forced there’s a maximum renewal counter. |
| 588 | +Nevertheless, some people recommend running this daily and just letting |
| 589 | +it fail until the server lets you renew. Others arrange to run it no |
| 590 | +more often than it’s known to work without complaint. Suit yourself. |
| 591 | + |
| 592 | + |
| 593 | +[acme]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment |
| 594 | +[cb]: https://certbot.eff.org/ |
| 595 | +[cbnu]: https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx |
| 596 | +[hsts]: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security |
| 597 | +[lja]: https://en.wikipedia.org/wiki/Logjam_(computer_security) |
| 598 | +[mitm]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack |
| 599 | +[nest]: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/ |
| 600 | +[ocsp]: https://en.wikipedia.org/wiki/OCSP_stapling |
| 601 | +[qslc]: https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices |
| 602 | +[qslt]: https://www.ssllabs.com/ssltest/ |
| 603 | + |
| 221 | 604 | *[Return to the top-level Fossil server article.](../)* |
| 222 | 605 | |