
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.
We will include the
Open Quantum Safe
library for post-quantum or quantum-safe
key encapsulation mechanism (KEM)
and digital signature algorithms.
The asummetric cryptography we have been using for key agreement
and digital signatures —
Diffie-Hellman
and
Elliptic-Curve Cryptography —
could be broken with a quantum computer with an adequate
number of adequately stable qubits.
We haven't seen one yet, but it seems to be on the way.
If you want to skip the quantum-safe part,
you could proceed directly to
configuring Nginx for an A+ Qualys score.
But, let's see how to be quantum-safe!
The steps are:
• Download OpenSSL, Nginx, and oqs-provider source code
• Build and install OpenSSL
• Build and install oqs-provider
• Configure OpenSSL to use oqs-provider
• Build and install Nginx
• Configure Nginx to use liboqs and oqs-provider
🚧
The quantum-safe sections of this are still under
development and test!
🚧
What Do We Need?
Try Google Cloud Platform and receive $50When 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 back then:
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 PlatformI 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.
VerifyingDigital
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.25.2.tar)= ce89d9ee713d3e8989a5b7ba2f6cc32b3c552afc50ece15da989ebf6124b4192 SHA256(openssl-3.1.2.tar)= 293f8443d1a57308c6a9dc1d3138aaa5cf94e9c95025458bc08e81867c28762f
Building The Software
Where should the software be installed? I have to decide that before I start building it. The standard locations are:
-
For booting the system,
rescues, and maintenance:
/
-
/bin
— user programs -
/sbin
— system programs -
/lib
— shared libraries
-
-
Full running environment:
/usr
-
/usr/bin
— user programs -
/usr/sbin
— system programs -
/usr/lib
— shared libraries -
/usr/share
— manual pages, data
-
-
Added packages:
/usr/local
-
/usr/local/bin
— user programs -
/usr/local/sbin
— system programs -
/usr/local/lib
— shared libraries -
/usr/local/share
— manual pages, data
-
I had my new custom-built OpenSSL install itself in its
default location under:
/usr/local/ssl/
and that will hold the
configuration file openssl.cnf
and other pieces.
The executable will be:
/usr/local/bin/openssl
and the new libraries will be:
/usr/local/lib/libcrypto.a
/usr/local/lib/libcrypto.so.3
/usr/local/lib/libssl.a
/usr/local/lib/libssl.so.3
However, I put the custom-built Nginx under:
/usr/local/nginx-version/
This way I could have multiple custom-built versions
of Nginx 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 really want to install
the latest version of OpenSSL,
versus simply use it.
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.
Accelerating the Software Compilation
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 ...]
The following describes compiling three packages,
each of which requires about an hour.
With one CPU that supports hyperthreading,
you might think that the
-j 2
option would make the builds run faster.
Yes, usually that would be the case —
use a parameter that is the number of CPUs,
twice that if they're hyperthreaded.
However, my server is on the Google Cloud platform,
at the Free Tier level.
That allows just a short burst of full CPU utilization
and then throttles it to 50% of possible maximum.
Compiling takes longer, but for me the price is right.
Here's what the Google Cloud console showed me
about CPU utilization while
make -j 2
had been running for almost 50 minutes to build OpenSSL:

Installing the Open Quantum Safe Library
The liboqs package may be available for your operating system. On FreeBSD you can add it with:
# pkg install liboqs
On FreeBSD use pkg info -l liboqs
to verify the package adds several *.h
files
in /usr/local/include/oqs
and a shared library
in /usr/local/lib/
with timestamps from when the package was built.
$ ls -l /usr/local/lib/liboqs.* lrwxr-xr-x 1 root wheel 11 Jul 5 2022 /usr/local/lib/liboqs.so@ -> liboqs.so.0 lrwxr-xr-x 1 root wheel 19 Jul 5 2022 /usr/local/lib/liboqs.so.0@ -> liboqs.so.0.7.2-dev -rwxr-xr-x 1 root wheel 10026872 Jul 5 2022 /usr/local/lib/liboqs.so.0.7.2-dev*
To build and install your own liboqs library,
see the project's
GitHub page
for full details.
But here's how to download the source code and
build a static liboqs
library.
I did this after installing the available compiled package:
$ git clone https://github.com/open-quantum-safe/liboqs.git $ cd liboqs $ cmake -S . -B _build [... narrative output ...] -- Configuring done -- Generating done -- Build files have been written to: /home/cromwell/liboqs/_build $ cmake --build _build [... narrative output, takes 55-58 minutes ...] $ su root -c 'cmake --install _build' [... narrative output ...] $ ls -l /usr/local/lib/liboqs.* -rw-r--r-- 1 root wheel 9350502 Apr 4 16:48 /usr/local/lib/liboqs.a lrwxr-xr-x 1 root wheel 11 Jul 5 2022 /usr/local/lib/liboqs.so@ -> liboqs.so.0 lrwxr-xr-x 1 root wheel 19 Jul 5 2022 /usr/local/lib/liboqs.so.0@ -> liboqs.so.0.7.2-dev -rwxr-xr-x 1 root wheel 10026872 Jul 5 2022 /usr/local/lib/liboqs.so.0.7.2-dev*
About FIPS Compliance
FIPS DocumentsNote that the FIPS 140-3 successor was approved on 22 March 2019 and became effective on 22 September 2019, although various documents continue to reference FIPS 140-2.
TL;DR:
If you need FIPS 140 compliance,
add enable-fips
to
the OpenSSL ./config
command,
and add --with-openssl-opt=enable-fips
to the Nginx ./configure
command.
"FIPS" refers to a formal U.S. Government standard, FIPS-140 (or the Federal Information Processing Standard 140). Versions FIPS 140-2 and 140-3 were published on 25 May 2001 and 22 March 2019, respectively. Those documents are short, just defining the legal requirements. The approved algorithms for encryption and decryption, hashing, and hashed message authentication codes (or HMAC) are defined in other publications — NIST Special Publications SP 800-140C and SP 800-140D, which in turn reference ISO/IEC 19790:2012(E) and ISO/IEC 24759:2017(E).
People casually speak of "FIPS" or "FIPS compliant" 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:
- It implements all the algorithms defined in the current NIST SP 800-140[CD] documents.
- Like #1 but also that the source code has been audited by U.S. NIST (National Institute of Standards and Technology) or the Canadian Centre for Cyber Security, and they found it to be a complete and correct implementation of the cipher suite.
- 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 you can find as:
/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/
Configuration Apache FIPS
Configuration OpenSSH FIPS
Configuration
Then you will need to reconfigure applications to only use
the approved algorithms.
See
my 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 #1 in my list 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 certified in August 2022.
Specific implementations and packaging by Oracle, Canonical/Ubuntu, Red Hat, and others, were under review by March 2023.
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.1.2.tar.xz
Configure OpenSSL
This is easy.
Beginning with OpenSSL 3.0.2, Perl must be installed.
With the enable-fips
parameter added,
it would build the FIPS module but it would not enforce its use.
I'm not specifying:
--prefix=/usr/local/ssl
--openssldir=/usr/local/ssl
because those are the defaults.
See the OpenSSL project's
Compilation and Installation page
for details on the available configuration options.
$ cd openssl-3.1.2 $ ./config Configuring OpenSSL version 3.1.2 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 and Install OpenSSL
I will need to build and install OpenSSL
under /usr/local
so I can specify its use of oqs-provider
.
The 1.1.1x series of OpenSSL required 12–14 minutes to compile on a single CPU, an Intel Xeon CPU running at 2.20 GHz.
On that same platform, the 3.1.x version requires 75 minutes to compile! Then the installation takes another 20 to 22 minutes with all its manual page construction.
As for the make test
step,
your newly compiled software
should just simply be ready to work correctly.
But you can run that test to be safe and certain.
It takes about 70 minutes.
$ make [... much output, about one hour of compiling on a single CPU ...] $ make test [... test output, this runs for about 70 minutes ...] $ su root -c 'make install' [... asked for root password, output of installing thousands of files, including generating manual pages with pod2man and mkpod2html.pl, takes 20-24 minutes to run ...]
Once it's installed, I can test it.
With its libraries in the expected
/usr/local/lib
I can use it without having to set the
LD_LIBRARY_PATH
environment variable.
$ /usr/local/bin/openssl version OpenSSL 3.1.2 1 Aug 2023 (Library: OpenSSL 3.1.2 1 Aug 2023) $ /usr/local/bin/openssl version -a OpenSSL 3.1.2 1 Aug 2023 (Library: OpenSSL 3.1.2 1 Aug 2023) built on: Fri Aug 11 11:43:36 2023 UTC platform: BSD-x86_64 options: bn(64,64) compiler: cc -fPIC -pthread -Wa,--noexecstack -Qunused-arguments -Wall -O3 -DL_ENDIAN -DOPENSSL_PIC -D_THREAD_SAFE -D_REENTRANT -DOPENSSL_BUILDING_OPENSSL -DNDEBUG OPENSSLDIR: "/usr/local/ssl" ENGINESDIR: "/usr/local/lib/engines-3" MODULESDIR: "/usr/local/lib/ossl-modules" Seeding source: os-specific CPUINFO: OPENSSL_ia32cap=0xfefa32035f8bffff:0x1c2ffb
Notice that the openssl
binary
is linked to use the libcrypto.so
and libssl.so
shared libraries
that the build created and installed in
/usr/local/lib
:
$ ldd /usr/local/bin/openssl /usr/local/bin/openssl: libssl.so.3 => /usr/local/lib/libssl.so.3 (0x11a726b70000) libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x11a725d63000) libthr.so.3 => /lib/libthr.so.3 (0x11a727b1d000) libc.so.7 => /lib/libc.so.7 (0x11a728262000) [vdso] (0x7ffffffff5d0) F
We will get our quantum-safe capability through the providers supported by OpenSSL 3. Notice that OpenSSL 1.* does not support providers, while OpenSSL 3 does.
$ which openssl /usr/bin/openssl $ openssl version OpenSSL 1.1.1o-freebsd 3 May 2022 $ openssl list -providers list: Option unknown option -providers list: Use -help for summary. $ /usr/local/bin/openssl version OpenSSL 3.1.2 1 Aug 2023 (Library: OpenSSL 3.1.2 1 Aug 2023) $ /usr/local/bin/openssl list -providers Providers: default name: OpenSSL Default Provider version: 3.1.2 status: active
As an alternative:
I can instead install the FreeBSD
openssl-devel
package.
It includes 135 *.h
files in
/usr/local/include/
and these libraries, notice the
.so.12
from the FreeBSD package instead of
.so.3
from what I built:
$ ls -l /usr/local/lib/{libcrypto,libssl}.* -rw-r--r-- 1 root wheel 8945540 Feb 20 05:01 /usr/local/lib/libcrypto.a lrwxr-xr-x 1 root wheel 15 Feb 20 05:01 /usr/local/lib/libcrypto.so@ -> libcrypto.so.12 -rwxr-xr-x 1 root wheel 4472984 Feb 20 05:01 /usr/local/lib/libcrypto.so.12* -rw-r--r-- 1 root wheel 1303186 Feb 20 05:01 /usr/local/lib/libssl.a lrwxr-xr-x 1 root wheel 12 Feb 20 05:01 /usr/local/lib/libssl.so@ -> libssl.so.12 -rwxr-xr-x 1 root wheel 690024 Feb 20 05:01 /usr/local/lib/libssl.so.12*
Building oqsprovider
At least this package builds quickly! However...
My naive approach was to simply follow the provided instructions:
$ git clone https://github.com/open-quantum-safe/oqs-provider $ cd oqs-provider $ cmake -DOPENSSL_ROOT_DIR=/usr/local -S . -B _build [... narrative output ...] -- Configuring done -- Generating done -- Build files have been written to: /home/cromwell/oqs-provider/_build $ cmake --build _build [... initial compilation steps ...]
However, this resulted in many errors about dynamic relocation:
[... earlier successful compilation steps ...] [ 64%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqs_endecoder_common.c.o [ 68%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqs_decode_der2key.c.o [ 72%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqsprov_bio.c.o [ 76%] Linking C shared library ../lib/oqsprovider.so ld: error: can't create dynamic relocation R_X86_64_32S against local symbol in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output >>> defined in ../install/lib/libqsc_key_encoder.a(encoding_qsc.c.o) >>> referenced by encoding_qsc.c >>> encoding_qsc.c.o:(qsc_encoding_by_name_oid) in archive ../install/lib/libqsc_key_encoder.a ld: error: can't create dynamic relocation R_X86_64_64 against symbol: Dilithium_R3_4x4_encodings in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output >>> defined in ../install/lib/libqsc_key_encoder.a(encoding_dilithium.c.o) >>> referenced by encoding_qsc.c >>> encoding_qsc.c.o:(qsc_encodings) in archive ../install/lib/libqsc_key_encoder.a ld: error: can't create dynamic relocation R_X86_64_64 against symbol: Dilithium_R3_6x5_encodings in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output >>> defined in ../install/lib/libqsc_key_encoder.a(encoding_dilithium.c.o) >>> referenced by encoding_qsc.c >>> encoding_qsc.c.o:(qsc_encodings) in archive ../install/lib/libqsc_key_encoder.a [... many continuing errors ...] ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors) cc: error: linker command failed with exit code 1 (use -v to see invocation) *** Error code 1 Stop.
The output tells us to use the -fPIC
option which
creates position-independent code (hence PIC)
suitable for dynamic linking and
avoiding any limit on the size of the global offset table.
I eventually figured out that the problem came from
FreeBSD including the Clang C compiler.
In this situation with everything being invoked through
cmake
,
the suggested workarounds
were specific to the GCC compiler collection and its
gcc
and g++
.
I also had GCC installed,
but it wasn't being used by the above process.
$ which cc /usr/bin/cc $ cc --version FreeBSD clang version 13.0.0 (git@github.com:llvm/llvm-project.git llvmorg-13.0.0-0-gd7b669b3a303) Target: x86_64-unknown-freebsd13.1 Thread model: posix InstalledDir: /usr/bin $ which gcc /usr/local/bin/gcc $ gcc --version gcc (FreeBSD Ports Collection) 12.2.0
Here is what worked for me.
I doubt that I needed to set
CXX
and CXXFLAGS
.
$ cd $ rm -r oqs-provider $ git clone https://github.com/open-quantum-safe/oqs-provider $ cd oqs-provider $ export CC=/usr/local/bin/gcc $ export CXX=/usr/local/bin/g++ $ export CFLAGS="-fPIC" $ export CXXFLAGS="-fPIC" $ cmake -DOPENSSL_ROOT_DIR=/usr/local -S . -B _build -- The C compiler identification is GNU 12.2.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /usr/local/bin/gcc - skipped -- Detecting C compile features -- Detecting C compile features - done -- Creating Release build -- Build will store public keys in PKCS#8 structures -- Build will include external encoding library for SPKI/PKCS#8 -- Found OpenSSL: /usr/local/lib/libcrypto.so (found suitable version "3.1.1", minimum required is "3.0") -- Building commit 0e46745 in /home/cromwell/oqs-provider CMake Warning at test/CMakeLists.txt:35 (message): OpenSSL source build directory '/home/cromwell/oqs-provider/test/../openssl' not present. Some dependent tests will be skipped. -- Configuring done -- Generating done -- Build files have been written to: /home/cromwell/oqs-provider/_build $ cmake --build _build [... many earlier compilation steps ...] [ 64%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqs_endecoder_common.c.o [ 68%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqs_decode_der2key.c.o [ 72%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqsprov_bio.c.o [ 76%] Linking C shared library ../lib/oqsprovider.so [ 76%] Built target oqsprovider [ 80%] Building C object test/CMakeFiles/oqs_test_signatures.dir/oqs_test_signatures.c.o [ 84%] Building C object test/CMakeFiles/oqs_test_signatures.dir/test_common.c.o [ 88%] Linking C executable oqs_test_signatures [ 88%] Built target oqs_test_signatures [ 92%] Building C object test/CMakeFiles/oqs_test_kems.dir/oqs_test_kems.c.o [ 96%] Building C object test/CMakeFiles/oqs_test_kems.dir/test_common.c.o [100%] Linking C executable oqs_test_kems [100%] Built target oqs_test_kems
That finished in just about ten seconds! Now I can install it:
$ su root -c 'cmake --install _build' -- Install configuration: "" -- Installing: /usr/local/lib/oqsprovider.so.0.5.0-dev -- Installing: /usr/local/lib/oqsprovider.so.1 -- Set runtime path of "/usr/local/lib/oqsprovider.so.0.5.0-dev" to "" -- Installing: /usr/local/lib/oqsprovider.so
Notice that the oqsprovider.so
shared library is also linked to use the
libcrypto.so
shared library
created when I built OpenSSL.
$ ls -l /usr/local/lib/oqsprovider.so* lrwxr-xr-x 1 root wheel 16 Apr 2 17:27 /usr/local/lib/oqsprovider.so -> oqsprovider.so.1 -rwxr-xr-x 1 root wheel 5939096 Apr 2 17:21 /usr/local/lib/oqsprovider.so.0.5.0-dev lrwxr-xr-x 1 root wheel 24 Apr 2 17:27 /usr/local/lib/oqsprovider.so.1 -> oqsprovider.so.0.5.0-dev $ file /usr/local/lib/oqsprovider.so.0.5.0-dev /usr/local/lib/oqsprovider.so.0.5.0-dev: ELF 64-bit LSB shared object, x86-64, version 1 (FreeBSD), dynamically linked, for FreeBSD 13.1, with debug_info, not stripped $ ldd /usr/local/lib/oqsprovider.so.0.5.0-dev /usr/local/lib/oqsprovider.so.0.5.0-dev: libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x1a4f365cb000) libc.so.7 => /lib/libc.so.7 (0x1a4f33538000) libthr.so.3 => /lib/libthr.so.3 (0x1a4f32b7f000)
Configuring OpenSSL to use oqsprovider
The openssl
program is configured by the file
openssl.cnf
.
The version that is part of FreeBSD uses:
Binary: /usr/bin/openssl
Configuration: /etc/ssl/openssl.cnf
For the newer version I built, that becomes:
Binary: /usr/local/bin/openssl
Configuration: /usr/local/ssl/openssl.cnf
Notice that there is a file named openssl.cnf.dist
which contains the original content.
Treat it like a critical backup file,
don't change it!
The file /usr/local/ssl/openssl.cnf
contains this section in lines 53 through 72:
[openssl_init] providers = provider_sect # List of providers to load [provider_sect] default = default_sect # The fips section name should match the section name inside the # included fipsmodule.cnf. # fips = fips_sect # If no providers are activated explicitly, the default one is activated implicitly. # See man 7 OSSL_PROVIDER-default for more details. # # If you add a section explicitly activating any other provider(s), you most # probably need to explicitly activate the default provider, otherwise it # becomes unavailable in openssl. As a consequence applications depending on # OpenSSL may not work correctly which could lead to significant system # problems including inability to remotely access the system. [default_sect] # activate = 1
We need to change it as follows, with changes highlighted:
[openssl_init] providers = provider_sect # List of providers to load [provider_sect] default = default_sect # The fips section name should match the section name inside the # included fipsmodule.cnf. # fips = fips_sect oqs = oqs_sect # If no providers are activated explicitly, the default one is activated implicitly. # See man 7 OSSL_PROVIDER-default for more details. # # If you add a section explicitly activating any other provider(s), you most # probably need to explicitly activate the default provider, otherwise it # becomes unavailable in openssl. As a consequence applications depending on # OpenSSL may not work correctly which could lead to significant system # problems including inability to remotely access the system. [default_sect] activate = 1 [oqs_sect] module = /usr/local/lib/oqsprovider.so activate = 1
Notice that I have:
-
Added
oqs
to the list of providers, to be defined in a following[oqs_sect]
section. -
Explicitly enabled the
default
provider, as strongly suggested. -
Created a new
[oqs_sect]
section which specifies the newly builtoqsprovider.so
shared library and then activates it.
The diff
command should show you these changes:
$ diff /usr/local/ssl/openssl.cnf.dist /usr/local/ssl/openssl.cnf 61a62 > oqs = oqs_sect 72c73 < # activate = 1 --- > activate = 1 73a75,77 > [oqs_sect] > module = /usr/local/lib/oqsprovider.so > activate = 1
Now the new openssl
program supports
the Open Quantum Safe provider!
$ /usr/local/bin/openssl list -providers Providers: default name: OpenSSL Default Provider version: 3.1.1 status: active oqs name: OpenSSL OQS Provider version: 0.5.0-dev status: active
For more details you could use:
$ /usr/local/bin/openssl list -providers -verbose -provider-path /usr/local/lib
[... much output ...]
Now Build Nginx
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 and liboqs 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.
Also notice the added
--with-cc-opt
and
--with-ld-opt
parameters, needed to use the liboqs library.
You'll need to figure out where the pieces are,
the below code works for the liboqs package on FreeBSD.
Something like the following, run as root, may be helpful:
# find / -name oqs -o -name liboqs.so\*
Expect to find some path to a directory
.../include/oqs/
and a symbolic link liboqs.so
pointing to a symbolic link liboqs.so.0
pointing to a file liboqs.so.0.7.2-dev
or similar;
you want the directory where those are located.
🚧
The quantum-safe aspects of this section are still under
development and test!
🚧
🚧
My remaining problem is that the nginx
binary
ends up linked to the original system
/lib/libcrypto.so.111
and not the 3.* library that I built.
The resulting program runs fine except that it
does not understand the ssl_ecdh_curve
parameters shown in the configuration file below.
🚧
$ cd $ tar xf nginx-1.25.2.tar.gz $ cd nginx-1.25.2 $ export LD_LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib $ ./configure --prefix=/usr/local/nginx-1.25.2 \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_v3_module \ --with-http_geoip_module \ --with-http_gunzip_module \ --with-http_sub_module \ --with-openssl=/home/cromwell/openssl-3.1.1 \ --with-cc-opt="-I /usr/local/include/oqs" \ --with-ld-opt="-L /usr/local/lib" [... much output ...] Configuration summary + using system PCRE2 library + using OpenSSL library: /home/cromwell/openssl-3.1.1 + using system zlib library nginx path prefix: "/usr/local/nginx-1.25.2" nginx binary file: "/usr/local/nginx-1.25.2/sbin/nginx" nginx modules path: "/usr/local/nginx-1.25.2/modules" nginx configuration prefix: "/usr/local/nginx-1.25.2/conf" nginx configuration file: "/usr/local/nginx-1.25.2/conf/nginx.conf" nginx pid file: "/usr/local/nginx-1.25.2/logs/nginx.pid" nginx error log file: "/usr/local/nginx-1.25.2/logs/error.log" nginx http access log file: "/usr/local/nginx-1.25.2/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" $ sed 's/libcrypto.a/libcrypto.a -loqs/' objs/Makefile > /tmp/Makefile $ mv /tmp/Makefile objs/Makefile # Note that because of a quirk in sed on FreeBSD and macos, the # following attempt to edit the file in place returns an error: # $ sed -i 's/libcrypto.a/libcrypto.a -loqs/' objs/Makefile # sed: 1: "objs/Makefile": invalid command code o # You could modify the sed command and edit the file in place: # $ sed -i .bak 's/libcrypto.a/libcrypto.a -loqs/' objs/Makefile # or do separate edit and move as shown above. $ make [... much output, about an hour 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
and 60 minutes with 3.1.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.
Notice that the liboqs.so.0
shared library
is in use.
🚧
The quantum-safe aspects of this section are still under
development and test!
🚧
$ 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 (0x800c92000) libpcre2-8.so.0 => /usr/local/lib/libpcre2-8.so.0 (0x800cb3000) liboqs.so.0 => /usr/local/lib/liboqs.so.0 (0x800e00000) libz.so.6 => /lib/libz.so.6 (0x800d73000) libGeoIP.so.1 => /usr/local/lib/libGeoIP.so.1 (0x800d90000) libc.so.7 => /lib/libc.so.7 (0x801793000) libthr.so.3 => /lib/libthr.so.3 (0x801b9d000) libcrypto.so.111 => /lib/libcrypto.so.111 (0x801bcb000)
The tail end of the build output shows that freshly compiled
libraries from OpenSSL 3 were used,
as directed by the modified
objs/Makefile
:
$ make [... many compilation steps ...] /usr/local/bin/gcc -o objs/nginx objs/src/core/nginx.o [... many *.o files listed ...] ... -L /usr/local/lib -lcrypt -lpcre2-8 ... /home/cromwell/openssl-3.1.2/.openssl/lib/libssl.a ... /home/cromwell/openssl-3.1.2/.openssl/lib/libcrypto.a ... -loqs -lz -lGeoIP -Wl,-E
🚧 While the resulting
nginx
works fine with
traditional cryptography, the following is still a problem.
🚧
However, setting the following in
/usr/local/nginx/conf/nginx.conf
:
ssl_ecdh_curve p256_frodo640aes:p256_bike1l1cpa:p256_kyber90s512:p256_ntru_hps2048509:p256_lightsaber:p256_sidhp434:p256_sikep434:X448:X25519:secp521r1:secp384r1;
results in:
# /usr/local/nginx/sbin/nginx -t nginx: [emerg] SSL_CTX_set1_curves_list("p256_frodo640aes:p256_bike1l1cpa:p256_kyber90s512:p256_ntru_hps2048509:p256_lightsaber:p256_sidhp434:p256_sikep434:X448:X25519:secp521r1:secp384r1") failed (SSL: error:0A080106:SSL routines::passed invalid argument:group 'p256_frodo640aes' cannot be set) nginx: configuration file /usr/local/nginx-1.25.2/conf/nginx.conf test failed
Change the ssl_ecdh_curve
string back, and:
# /usr/local/nginx/sbin/nginx -t nginx: the configuration file /usr/local/nginx-1.25.2/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx-1.25.2/conf/nginx.conf test is successful
I had worked on this earlier,
and returned to it in early April 2023.
I suspect that the problem is caused by the build
linking the nginx
binary with the
/lib/libcrypto.so.111
shared library
that's from OpenSSL 1.1.1 installed with FreeBSD 13.1
instead of the
/usr/local/lib/libcrypto.so.3
shared library built and installed from OpenSSL 3.1.2.
I had set LD_LIBRARY_PATH
with
/usr/local/lib
first in the list,
but this persisted.
At the time there were ways to get quantum-safe
cryptography in Nginx,
but everything I found used modified OpenSSL 1.1.1 code.
For example:
Lan Itan's blog
Samuel Jacquier's repository
Meanwhile, The OpenSSL project reminded us that OpenSSL 1.1.1 would reach End Of Life in September 2023. And, FreeBSD releases were on the way — 13.2 in mid-April 2023, and more significantly, 14.0 later in 2023. I decided to set this aside until FreeBSD 14.0 and see if it wasn't built around OpenSSL 3.
Now I will make symbolic links so generic names point to the current versions.
$ su # cd /usr/local # rm -f nginx # ln -s nginx-1.25.2 nginx
And, to get the new version pointed to my configuration and content, change this as needed:
# cd /usr/local/nginx-1.25.2 # 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.25.2/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx-1.25.2/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/RSACertificates
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;
# If you do not have the quantum-safe part running, uncomment the following line
# and comment out the longer one with parameters starting "p256_frodo640aes:...".
# Otherwise, your server will not start!
# ssl_ecdh_curve X448:X25519:secp521r1:secp384r1;
ssl_ecdh_curve p256_frodo640aes:p256_bike1l1cpa:p256_kyber90s512:p256_ntru_hps2048509:p256_lightsaber:p256_sidhp434:p256_sikep434:X448:X25519:secp521r1:secp384r1;
}
}
🚧 The quantum-safe sections of this are still under development and test! 🚧
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 able 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.

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 HTTPSSecurity
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, key exchange elliptic curve, and symmetric 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
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
Advanced topic: Consider using Poudriere to create and test these as FreeBSD packages.