Servers.

Building Nginx and OpenSSL from Source

The Project So Far

Getting Started: Log Analysis and TLS Versions Configuring Nginx for Security and Performance

This page explains how to build both the cryptographic toolkit OpenSSL and the web server Nginx from source code.

You shouldn't need to do this now, you should be able to proceed directly to configuring Nginx for an A+ Qualys score. That is, unless you're stuck using an old Linux distribution. If you are in that situation, then whatever requirement keeps you on an old Linux release might also prevent using software downloaded and built from source.

But, to help those people who do need to build their own OpenSSL or Nginx software, let's see how to do it!

What Do We Need?

Try Google Cloud Platform and receive $50

When I first did this project in September, 2018, the Apache web server did not support TLS 1.3. It didn't matter if you had the needed shared libraries. Apache still thought that a directive to include TLS 1.3 was incorrect configuration syntax. From the Apache release notes:

This release is compatible with OpenSSL versions from 0.9.8a to 1.1.0 only, and does not support TLSv1.3. Future releases of httpd 2.4 are expected to add compatibility with OpenSSL 1.1.1 and enable support for TLSv1.3.

There were some patches that could make it happen, but that would make ongoing maintenance too much of a hassle.

Nginx was the clear choice of web server. It uses the OpenSSL cryptographic libraries to do TLS.

FreeBSD on Google Compute Platform

I was doing this project on FreeBSD, where OpenSSL is included in the base operating system and Nginx is a standard package. However, FreeBSD 11.2-RELEASE included earlier versions: OpenSSL 1.0.2o and Nginx 1.14.0. (That version of Nginx supported TLS 1.3, but it was compiled to use the standard system OpenSSL libraries, which I didn't want to replace.) At the time, Mageia Linux had OpenSSL 1.0.2p, and Mint Linux had OpenSSL 1.1.0g.

The FreeBSD Ports system provides a way of getting more or less the same things. I needed to build Nginx and OpenSSL from source.

Getting The Software

First, download the current version of Nginx.

OpenSSL 1.1.1 had been released on September 11th, 2018, just 3 to 4 weeks after RFC 8446 was published, formally defining TLS 1.3. There had been a series of 28 drafts of that document. Some earlier versions of OpenSSL support Draft #28, which ended up being the final definition. But let's start with the official versions!

Three years later, in September 2021, OpenSSL 3.0.0 was released. Download the current version of OpenSSL.

Verifying
Digital
Signatures

Download the compressed archive files, along with associated digital signatures and hashes.
nginx-x.y.z.tar.gz
nginx-x.y.z.tar.gz.asc
openssl-x.y.z.tar.gz
openssl-x.y.z.tar.gz.sha256

Verify digital signatures and hashes, and make sure you're getting the real software from the original organization.

Here's what I get for the SHA-2-256 hashes of recent versions of the uncompressed archives:

$ openssl sha256 *.tar
SHA256(nginx-1.23.3.tar)= 4738f810fb2c595ee6d00ef286061879c12dd706b761aea9210b2f64731df13e
SHA256(openssl-3.0.8.tar)= cd9806b97893638dfa54fc2dd40dad21a1e22a668001705e7a104d5176625ec1

About FIPS Compliance

FIPS Documents

Note that a FIPS 140-3 successor was approved on 22 March 2019 and became effective on 22 September 2019, although it was still in a rather preliminary state as of early to mid 2022.

Simple mention of "FIPS" is a casual reference to a formal U.S. Government standard, FIPS-140-2 (or the Federal Information Processing Standard 140-2), published on 25 May 2001. That document enumerates some cryptographic algorithms for encryption and decryption, hashing, and hashed message authentication codes (or HMAC), and it also involves the certification, approval, or acceptance of implementations of those.

Anyone can speak of "FIPS" or "FIPS compliant" in various ways, often without carefully thinking about what they mean or what the terminology may imply.

Let's be careful. Saying that a software package or shared library "implements the FIPS cipher suite" or that it is "FIPS compliant" might mean any of the following, in increasing order of significance and formality:

  1. It implements all the algorithms defined in the current FIPS-140-* document.
  2. Like #1 but also that the source code has been audited by U.S. NIST (National Institute of Standards and Technology, considered the authority by other US government agencies) and they found it to be a complete and correct implementation of the cipher suite.
  3. Like #2 but also that the vendor (of the distribution, for Linux, or the organization for, e.g., FreeBSD) has been approved as a trusted group with an approved patch notification system, including digitally signed update packages. This includes both technical analysis of the code and organizational approval of the vendor.

Finally, you can, and for U.S. Government compliance you must, disable the use of non-FIPS ciphers and hash functions. First, do this for the kernel to restrict its use of algorithms in IPsec and encrypted storage. Add fips=1 to the kernel command line by adding it to the file /etc/default/grub, rebuilding the GRUB configuration files, and rebooting.

# grep GRUB_CMDLINE_LINUX /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet fips=1"
GRUB_CMDLINE_LINUX="quiet fips=1"
# update-grub
# reboot

Beware that systemd meddling on RHEL 8 and CentOS Stream 8 very likely breaks the above simple solution, forcing you to deal with the file:
/boot/loader/entries/$(cat /etc/machine-id)-$(uname -r).conf
See these pages for hints:
https://access.redhat.com/solution/3710121
https://access.redhat.com/solution/3766391
https://systemd.io/BOOT_LOADER_SPECIFICATION
https://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/

Nginx FIPS
Configuration
Apache FIPS
Configuration
OpenSSH FIPS
Configuration

Then you will need to reconfigure applications to only use the approved algorithms. See the next page in this series for how to do that for Nginx, or see this page for Apache's httpd web server. For the SSH service, edit /etc/ssh/sshd_config and specify FIPS algorithms for the Ciphers and MACs parameters. See the sshd_config manual page for details.

Level #1 in my numbered list above is basically "Hey, we tried" and no more, while #3 is required in a U.S. Department of Defense setting. What do you need? If you are working for U.S. DoD or some other U.S. Government agencies, or industry working for them, you need #3. Otherwise, you must decide this for yourself.

Another way to think of this is that my #1 is about capability, but without any formal analysis or trust, while #3 is about analysis and certification of code and processes by a trusted entity. Unless you must operate within U.S. Government constraints, there is no simple "correct answer" here.

The OpenSSL 3.0 FIPS module was submitted for validation in September 2021, turned in to US NIST's Cryptographic Module Validation Program.

Specific implementations and packaging by Oracle, Canonical/Ubuntu, Red Hat, and others, plus the OpenSSL project itself, were under review by February 2022.

Building The Software

Where should the software be installed? I have to decide that before I start building it. The standard locations are:

I didn't want to use any of those! I would put the new custom-built OpenSSL under /usr/local/openssl-version and the new custom-built Nginx under /usr/local/nginx-version. They would create their own hierarchy of subdirectories, with bin, sbin, lib, and share having the same meanings. This way I could have multiple custom-built versions installed at the same time. I could test the very latest but still have the previous one(s) available as a fall-back. Then I could easily remove an older version I no longer need.

You also need to decide whether or not you want to install the latest version of OpenSSL. When you build Nginx, it compiles the OpenSSL code to create static libraries libssl.a and libcrypto.a and then embeds them within the nginx binary. So, you don't have to independently build OpenSSL to build the latest Nginx using the latest OpenSSL.

Extract The OpenSSL Archive

Extract the OpenSSL source code archive. In the following examples, I did this in my home directory.

$ tar xf openssl-3.0.8.tar.xz

The compile times reported in the following are what I saw on my server, a FreeBSD system running in the Google Cloud Platform. Running dmesg immediately after booting reports this about the CPU and memory:

[... lines omitted ...]
CPU: Intel(R) Xeon(R) CPU @ 2.20GHz (2200.28-MHz K8-class CPU)
  Origin="GenuineIntel"  Id=0x406f0  Family=0x6  Model=0x4f  Stepping=0
  Features=0x1f83fbff<FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36,MMX,FXSR,SSE,SSE2,SS,HTT>
  Features2=0xfefa3203<SSE3,PCLMULQDQ,SSSE3,FMA,CX16,PCID,SSE4.1,SSE4.2,x2APIC,MOVBE,POPCNT,AESNI,XSAVE,OSXSAVE,AVX,F16C,RDRAND,HV>
  AMD Features=0x2c100800<SYSCALL,NX,Page1GB,RDTSCP,LM>
  AMD Features2=0x121<LAHF,ABM,Prefetch>
  Structured Extended Features=0x1c2ffb<FSGSBASE,TSCADJ,BMI1,HLE,AVX2,FDPEXC,SMEP,BMI2,ERMS,INVPCID,RTM,NFPUSG,RDSEED,ADX,SMAP>
  Structured Extended Features3=0xac000400<MD_CLEAR,IBPB,STIBP,ARCH_CAP,SSBD>
  XSAVE Features=0x1<XSAVEOPT>
  IA32_ARCH_CAPS=0x4c<RSBA,SKIP_L1DFL_VME>
  TSC: P-state invariant
Hypervisor: Origin = "KVMKVMKVM"
real memory  = 1073741824 (1024 MB)
avail memory = 1004154880 (957 MB)
Event timer "LAPIC" quality 600
ACPI APIC Table: <Google GOOGAPIC>
FreeBSD/SMP: Multiprocessor System Detected: 2 CPUs
FreeBSD/SMP: 1 package(s) x 1 core(s) x 2 hardware threads
[... many more lines omitted ...]

Configure OpenSSL

This is easy. Beginning with OpenSSL 3.0.2, Perl must be installed. With the enable-fips parameter included, it will build the FIPS module but it will not enforce its use.

$ cd openssl-3.0.8
$ ./config --prefix=/usr/local/openssl-3.0.8 \
		--openssldir=/usr/local/openssl-3.0.8 \
		enable-fips 
Configuring OpenSSL version 3.0.8 for target BSD-x86_64
Using os-specific seed configuration
Creating configdata.pm
Running configdata.pm
Creating Makefile.in
Creating Makefile
Created include/openssl/configuration.h

**********************************************************************
***                                                                ***
***   OpenSSL has been successfully configured                     ***
***                                                                ***
***   If you encounter a problem while building, please open an    ***
***   issue on GitHub <https://github.com/openssl/openssl/issues>  ***
***   and include the output from the following command:           ***
***                                                                ***
***       perl configdata.pm --dump                                ***
***                                                                ***
***   (If you are new to OpenSSL, you might want to consult the    ***
***   'Troubleshooting' section in the INSTALL file first)         ***
***                                                                ***
**********************************************************************

Build OpenSSL, If You Want To

Once configured you can skip ahead

Continue with this section if you want to build and install the latest OpenSSL command and shared libraries. Or, if you just want to build Nginx with static libraries for the latest OpenSSL, you can skip ahead once you have configured OpenSSL.

The 1.1.1x series of OpenSSL required 12–14 minutes to compile on a single CPU.

The 3.0.x version requires over one hour!

$ make
[... much output, about one hour of compiling on a single CPU ...]
$ su root -c 'make install'
[... asked for root password, output of installing thousands of files,
     including generating manual pages with pod2man and mkpod2html.pl ...]

Once it's installed, you have to set the LD_LIBRARY_PATH environment variable before you can use the new binary.

$ /usr/local/openssl-3.0.8/bin/openssl version
ld-elf.so.1: Shared object "libssl.so.3" not found, required by "openssl"
$ bash
$ export LD_LIBRARY_PATH=/usr/local/openssl-3.0.8/lib
$ /usr/local/openssl-3.0.8/bin/openssl version
OpenSSL 3.0.8 7 Feb 2023 (Library: OpenSSL 3.0.8 7 Feb 2023)
$ exit 

You can similarly ask for the manual pages from the new custom version.

$ man ciphers
[... manual page for software included with the OS ...]
$ man -M /usr/local/openssl-3.0.8/share/man ciphers
[... manual page for custom-built version 3.0.8 ...] 

You might have noticed that version 1.1.1* supported fewer ciphers than 1.0.2*. This is because the newer versions are more secure, by refusing to use weaker ciphers like DES, 3DES, and RC4. Compare the results of these two commands:

$ openssl ciphers | egrep --color 'DES|RC4'
[... output including DES, 3DES, and RC4 variants ...]
$ /usr/local/openssl-3.0.8/bin/openssl ciphers | egrep --color 'DES|RC4'
[... no output ...]

Now Build Nginx

Advanced topic: Consider using Poudriere to create and test these as FreeBSD packages.

Thanks to Lukáš Hamrla for spotting my oversight about the --with-openssl-opt parameter! See the Nginx build configuration page for details.

Now we can build Nginx, telling it where to install itself, what to build, and where to find the OpenSSL code. Notice that you tell it where the OpenSSL source code is located, and not where the openssl program and libraries and manual pages were installed. Change /home/cromwell in the below to your home directory, or wherever you have the OpenSSL source code. Notice the added --with-openssl-opt=enable-fips parameter. That's needed, as otherwise it will not use the enable-fips parameter included earlier.

$ cd
$ tar xf nginx-1.23.3.tar.gz
$ cd nginx-1.23.3
$ ./configure --prefix=/usr/local/nginx-1.23.3	\
		--with-http_ssl_module			\
		--with-http_v2_module			\
		--with-http_geoip_module		\
		--with-http_gunzip_module		\
		--with-http_sub_module			\
		--with-openssl=/home/cromwell/openssl-3.0.8 \
		--with-openssl-opt=enable-fips
[... much output ...]

Configuration summary
  + using system PCRE2 library
  + using OpenSSL library: /home/cromwell/openssl-3.0.8
  + using system zlib library

  nginx path prefix: "/usr/local/nginx-1.23.3"
  nginx binary file: "/usr/local/nginx-1.23.3/sbin/nginx"
  nginx modules path: "/usr/local/nginx-1.23.3/modules"
  nginx configuration prefix: "/usr/local/nginx-1.23.3/conf"
  nginx configuration file: "/usr/local/nginx-1.23.3/conf/nginx.conf"
  nginx pid file: "/usr/local/nginx-1.23.3/logs/nginx.pid"
  nginx error log file: "/usr/local/nginx-1.23.3/logs/error.log"
  nginx http access log file: "/usr/local/nginx-1.23.3/logs/access.log"
  nginx http client request body temporary files: "client_body_temp"
  nginx http proxy temporary files: "proxy_temp"
  nginx http fastcgi temporary files: "fastcgi_temp"
  nginx http uwsgi temporary files: "uwsgi_temp"
  nginx http scgi temporary files: "scgi_temp"

$ make
[... much output, about 48-54 minutes of compiling ...]
$ su root -c 'make install'
[... asked for root password, much output ...] 

Compiling Nginx also takes much longer with OpenSSL 3: about 14–16 minutes with OpenSSL 1.1.1x, but 48–54 minutes with 3.0.x. This is because whether you already compiled and installed OpenSSL or not, this recompiles the static libraries and embeds them in the nginx binary.

$ file nginx
nginx: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 13.1, FreeBSD-style, with debug_info, not stripped
$ ldd nginx
nginx:
	libcrypt.so.5 => /lib/libcrypt.so.5 (0x800723000)
	libpcre2-8.so.0 => /usr/local/lib/libpcre2-8.so.0 (0x800744000)
	libz.so.6 => /lib/libz.so.6 (0x800804000)
	libGeoIP.so.1 => /usr/local/lib/libGeoIP.so.1 (0x800821000)
	libc.so.7 => /lib/libc.so.7 (0x80086c000)
	libthr.so.3 => /lib/libthr.so.3 (0x800c76000) 

Now I will make symbolic links so generic names point to the current versions.

$ su
# cd /usr/local
# rm -f openssl nginx
# ln -s openssl-3.0.8 openssl
# ln -s nginx-1.23.3 nginx 

And, to get the new version pointed to my configuration and content, change this as needed:

# cd /usr/local/nginx-1.23.3
# mv html html-original
# ln -s /home/cromwell/www html
# ln -s /home/cromwell/www/nginx/ssl_dhparam ssl_dhparam
# rm conf/nginx.conf
# ln -s /home/cromwell/www/nginx/nginx.conf conf/nginx.conf 

Make sure that the new software, your configuration file, and the symbolic links are all ready to go:

# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx-1.23.3/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx-1.23.3/conf/nginx.conf test is successful 

I need to stop the running Apache process, and comment out the line in /etc/rc.conf that starts it at boot time.

$ grep apache /etc/rc.conf
## apache24_enable=YES 

I will create or modify /etc/rc.local to start nginx at boot time.

$ cat /etc/rc.local
#!/bin/sh

# Created just to start Nginx
/usr/local/nginx/sbin/nginx 

Initial Configuration

Dual ECC/RSA
Certificates

The web server should be ready to go once we define a simple configuration. Here are the contents of a basic nginx.conf file. This uses dual ECC and RSA keys and certificates. See my earlier page for details on how to generate and install these, and automatically renew them.

# Nginx configuration

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    index  Index.html;

    # HTTP server
    server {
	listen       80;
	server_name  cromwell-intl.com;
	# gzip suggestions from:
	# https://www.digitalocean.com/community/tutorials/how-to-increase-pagespeed-score-by-changing-your-nginx-configuration-on-ubuntu-16-04
	gzip on;
	gzip_comp_level 5;
	gzip_min_length 256;
	gzip_vary on;
	gzip_types text/plain text/css application/javascript application/x-javascript text/javascript image/x-icon application/x-font-ttf;

	return 301 https://$host$request_uri;

        location / {
	    root   html;
	}

	error_page 404 /ssi/404page.html;

    }

    # HTTPS server
    server {
	listen       443 ssl http2;
	server_name  cromwell-intl.com;
	keepalive_timeout 65;
	# gzip suggestions from:
	# https://www.digitalocean.com/community/tutorials/how-to-increase-pagespeed-score-by-changing-your-nginx-configuration-on-ubuntu-16-04
	gzip on;
	gzip_comp_level 5;
	gzip_min_length 256;
	gzip_vary on;
	gzip_types text/plain text/css application/javascript application/x-javascript text/javascript image/x-icon application/x-font-ttf;

	location / {
	    root   html;
	}

	error_page 404 /ssi/404page.html;

	####################################################################
	# Certificates and private keys.
	####################################################################

	# ECC
	ssl_certificate /usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/fullchain.pem;
	ssl_certificate_key /usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/privkey.pem;
	# RSA
	ssl_certificate /usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/fullchain.pem;
	ssl_certificate_key /usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/privkey.pem;

	####################################################################
	## Cryptography and TLS
	####################################################################

	ssl_protocols TLSv1.2 TLSv1.3;

	# SSL session cache timeout defaults to 5 minutes, 1 minute should
	# be plenty.  This is abused by advertisers like Google and Facebook,
	# long timeouts like theirs will look suspicious.  See, for example:
	# https://www.zdnet.com/article/advertisers-can-track-users-across-the-internet-via-tls-session-resumption/
	ssl_session_cache    shared:SSL:1m;
	ssl_session_timeout  5m;

	ssl_ciphers EECDH:ECDHE:EDH;

	ssl_prefer_server_ciphers on;

    }

} 

Start it, and make sure it's running on TCP ports 80 and 443. You will see a master process running as root. There should be a worker process running as nginx and listening on TCP/80 and TCP/443, highlighted here. Then lsof will also show the worker caught in the act of serving content.

# lsof -i tcp:80 -o -i tcp:443
COMMAND     PID   USER   FD   TYPE             DEVICE OFFSET NODE NAME
nginx     11117   root    9u  IPv4 0xfffff80024dde820    0t0  TCP *:http (LISTEN)
nginx     11117   root   10u  IPv4 0xfffff80024e4a410    0t0  TCP *:https (LISTEN)
nginx     11118 nobody    3u  IPv4 0xfffff80024d9e820    0t0  TCP www.c.cromwell-intl.internal:https->74-216-249-130.dedicated.allstream.net:54781 (ESTABLISHED)
nginx     11118 nobody    9u  IPv4 0xfffff80024dde820    0t0  TCP *:http (LISTEN)
nginx     11118 nobody   10u  IPv4 0xfffff80024e4a410    0t0  TCP *:https (LISTEN)
nginx     11118 nobody   13u  IPv4 0xfffff80024900820    0t0  TCP www.c.cromwell-intl.internal:https->ec2-54-172-123-82.compute-1.amazonaws.com:37726 (ESTABLISHED)
nginx     11118 nobody   14u  IPv4 0xfffff80024d98820    0t0  TCP www.c.cromwell-intl.internal:https->66.87.176.179:25208 (ESTABLISHED)
nginx     11118 nobody   15u  IPv4 0xfffff80024b22820    0t0  TCP www.c.cromwell-intl.internal:https->d24-36-29-150.home1.cgocable.net:55150 (ESTABLISHED)
nginx     11118 nobody   16u  IPv4 0xfffff80024b20410    0t0  TCP www.c.cromwell-intl.internal:https->71-80-191-91.static.lsan.ca.charter.com:62074 (ESTABLISHED)
nginx     11118 nobody   17u  IPv4 0xfffff80024e18820    0t0  TCP www.c.cromwell-intl.internal:https->ip72-207-111-102.sd.sd.cox.net:56285 (ESTABLISHED)
nginx     11118 nobody   18u  IPv4 0xfffff80024d9e000    0t0  TCP www.c.cromwell-intl.internal:http->180.115.150.200.static.copel.net:44473 (ESTABLISHED)
nginx     11118 nobody   19u  IPv4 0xfffff80024384410    0t0  TCP www.c.cromwell-intl.internal:https->8.30.181.58:54251 (ESTABLISHED)
nginx     11118 nobody   20u  IPv4 0xfffff80024f1a000    0t0  TCP www.c.cromwell-intl.internal:https->mail.iph-bet.fr:49700 (ESTABLISHED)
nginx     11118 nobody   22u  IPv4 0xfffff80024d9d410    0t0  TCP www.c.cromwell-intl.internal:https->180.115.150.200.static.copel.net:54777 (ESTABLISHED)
nginx     11118 nobody   23u  IPv4 0xfffff800247e4820    0t0  TCP www.c.cromwell-intl.internal:https->CPE84948c4cc1b3-CM84948c4cc1b0.cpe.net.cable.rogers.com:54286 (ESTABLISHED)
nginx     11118 nobody   24u  IPv4 0xfffff80024d98000    0t0  TCP www.c.cromwell-intl.internal:https->CPE84948c4cc1b3-CM84948c4cc1b0.cpe.net.cable.rogers.com:54287 (ESTABLISHED)
nginx     11118 nobody   25u  IPv4 0xfffff800247f5820    0t0  TCP www.c.cromwell-intl.internal:https->CPE84948c4cc1b3-CM84948c4cc1b0.cpe.net.cable.rogers.com:54289 (ESTABLISHED)
nginx     11118 nobody   26u  IPv4 0xfffff8002490e410    0t0  TCP www.c.cromwell-intl.internal:https->CPE84948c4cc1b3-CM84948c4cc1b0.cpe.net.cable.rogers.com:54288 (ESTABLISHED)
nginx     11118 nobody   27u  IPv4 0xfffff80024dde410    0t0  TCP www.c.cromwell-intl.internal:https->172.56.42.132:61366 (ESTABLISHED)
nginx     11118 nobody   28u  IPv4 0xfffff80024384820    0t0  TCP www.c.cromwell-intl.internal:https->190.106.223.59:45182 (ESTABLISHED)
nginx     11118 nobody   29u  IPv4 0xfffff80024d99000    0t0  TCP www.c.cromwell-intl.internal:https->dynamicip-94-181-133-82.pppoe.penza.ertelecom.ru:55386 (ESTABLISHED)
nginx     11118 nobody   56u  IPv4 0xfffff80024e4a000    0t0  TCP www.c.cromwell-intl.internal:https->66.87.176.179:30154 (ESTABLISHED)
python2.7 12908   root    7u  IPv4 0xfffff80024d9f000    0t0  TCP www.c.cromwell-intl.internal:55720->metadata.google.internal:http (ESTABLISHED)

It Should Be Running, But Old Tools Might Not Realize That

The problem is that our measurement tools may fail to show the TLS functionality. Your web browser, the (default) OpenSSL package, the Wireshark protocol analyzer, and online scanners should all be unable to test or recognize TLS 1.3. If not, you need to update your tools!

Make sure you can test your server from a distance:

$ openssl s_client -tls1_3 cromwell-intl.com:443
CONNECTED(00000005)
---
Certificate chain
 0 s:CN = cromwell-intl.com
   i:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
 1 s:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
   i:O = Digital Signature Trust Co., CN = DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFdzCCBF+gAwIBAgISAzBCb9tcMGcrVV2eqVLf7J39MA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
[... lines deleted ...]
sboGITUCwIdEGAPawfevVlv0xKG2g0oZK3dqhTXNIOuq3euzHf+NA+iUXVrO6ll7
Vy/rjUpz225dyEBtPULkwrVvN5bpNKutKgH8HE++mII47+sv3ef+jRs6b3/95m5f
Oz7WyhK2c3R0mGo=
-----END CERTIFICATE-----
subject=CN = cromwell-intl.com

issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3

---
No client certificate CA names sent
Peer signing digest: SHA384
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2986 bytes and written 321 bytes
Verification error: unable to get local issuer certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 384 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 20 (unable to get local issuer certificate)
---
QUIT 

Yes! It works!

I captured that traffic with Wireshark. On a recent distribution, Wireshark can figure it out.

Wireshark analyzing TLS 1.3 traffic

Also try testing it with the Qualys web server evaluation scanner. Their development scanner at dev.ssllabs.com may be able to do additional tests.

We Can Do Better

Tuning HTTPS
Security
on Apache

In an earlier series of pages I describe how to configure an Apache web server to score an A+ grade from Qualys and from online HTTP header analyzers. Here's how to do the same thing with Nginx.

I pass two variables to the PHP back end processor, which allows me to put the TLS protocol version and cipher in the standard footer on each page. The code in the footer looks like this:

<p style="text-align:right; font-size:0.80rem;">
<?php	/* The trick here is to pass these values to the PHP processor
	 * in the Nginx configuration file:
	 *	fastcgi_param  TLS_PROTOCOL $ssl_protocol;
	 *	fastcgi_param  TLS_CURVE $ssl_curve;
	 *	fastcgi_param  TLS_CIPHER $ssl_cipher;
	 */
	echo('Protocol: ' . $_SERVER["SERVER_PROTOCOL"] . '<br />');
	echo('Crypto: ' . $_SERVER["TLS_PROTOCOL"] . ' / ' $_SERVER["TLS_CURVE"] . ' / ' . $_SERVER["TLS_CIPHER"] ); ?>
</p>

The result, specific to your connection to view this page, is:

Protocol: HTTP/1.1
Crypto: TLSv1.3 / X25519 / TLS_AES_256_GCM_SHA384

Maintaining Dual ECC and RSA Certificates

You need at least one key pair, with the public key wrapped in a digital certification issued by a trusted CA (or Certificate Authority). Another page of mine shows how to generate and maintain dual ECC and RSA certificates from the Let's Encrypt project, automatically renewing them.

Configuring the Server

Now you're ready for the last step:
Configuring Nginx for an A+ Qualys Score