TT Lab
Get started
Learn Learning paths Courses

Tomcat & nginx Operations

Issuing a Certificate From a Private CA and Applying It to nginx

Continue in TT Lab

Goal

You create a private CA, issue a server certificate that includes a SAN, build a chain file, apply HTTPS to nginx, and become able to build an expiry monitoring script as well.

Why it matters

Most certificate outages come not from cryptography but from two things: a missing chain and expiry. If you leave out the intermediate certificate, it works in the browser but fails only in the integrated counterpart system. That is because browsers cache intermediate certificates or fetch them on their own, but server-to-server HTTP clients don't. "It works on my developer PC" is the typical symptom. And expiry doesn't come without warning, yet every year there are incidents because of owner changes and missed alerts. If you build it yourself once with a private CA, this structure stays in your hands.

Steps

  1. Create the /root/ng/tls directory and generate a 2048-bit RSA private key as /root/ng/tls/server.key.
  2. Create a CSR with that key and save it as /root/ng/tls/server.csr. The subject must include CN=labhub.local, O=LabHub and C=KR.
  3. Create a private CA. /root/ng/tls/ca.key and /root/ng/tls/ca.crt. The subject of the CA certificate is CN=LabHub Root CA, the validity must be 3650 days or more, and basicConstraints must have CA:TRUE.
  4. Sign the CSR with the CA to create /root/ng/tls/server.crt. The SAN must contain both DNS:labhub.local and IP:127.0.0.1, and the issuer must be the same as the CA's subject.
  5. Create /root/ng/tls/fullchain.pem. Concatenate in the order server certificate → CA certificate, and the file must contain exactly 2 certificates. openssl verify -CAfile ca.crt server.crt must succeed.
  6. Add an HTTPS server block on port 8443 to nginx. Set ssl_certificate to fullchain and ssl_certificate_key to server.key, and for ssl_protocols allow only TLSv1.2 TLSv1.3. The output of openssl s_client -connect 127.0.0.1:8443 -CAfile /root/ng/tls/ca.crt must show Verify return code: 0.
  7. Make all requests on port 8088 be redirected with a 301 to https://. The result of curl -s -o /dev/null -w '%{http_code} %{redirect_url}' http://127.0.0.1:8088/x must be 301, and the redirect URL must start with https:// and contain 8443.
  8. Create /root/ng/certcheck.sh. It takes two arguments (인증서파일 임계일수, the certificate file and the threshold in days) and ends with exit code 0 if the remaining validity is more than the threshold days, and with a non-zero exit code if expiry is within the threshold days. If the certificate file doesn't exist, it must also end with a non-zero exit code.

Notes

Generate the server private key

Create the /root/ng/tls directory and generate a 2048-bit RSA private key as /root/ng/tls/server.key.

2048-bit RSA is still the standard choice. Also learn the openssl subcommand that checks that the generated key is valid.

Generate the CSR

Create a CSR with that key and save it as /root/ng/tls/server.csr. The subject must include CN=labhub.local, O=LabHub and C=KR.

You can give the CSR subject information all at once with -subj. The country code is two uppercase letters.

Create a private CA

Create a private CA. /root/ng/tls/ca.key and /root/ng/tls/ca.crt. The subject of the CA certificate is CN=LabHub Root CA, the validity must be 3650 days or more, and basicConstraints must have CA:TRUE.

A CA certificate must differ from an ordinary server certificate. Check which extension marks it as a CA. If you give -x509 to openssl req, you get a self-signed certificate.

Sign the server certificate with the CA

Sign the CSR with the CA to create /root/ng/tls/server.crt. The SAN must contain both DNS:labhub.local and IP:127.0.0.1, and the issuer must be the same as the CA's subject.

Modern browsers and libraries look at the SAN, not the CN. When signing with openssl x509, you must pass the extension as a file for the SAN to be included.

Build the chain file

Create /root/ng/tls/fullchain.pem. Concatenate in the order server certificate → CA certificate, and the file must contain exactly 2 certificates. openssl verify -CAfile ca.crt server.crt must succeed.

Concatenate the server certificate and the CA certificate. The order matters. After creating it, confirm with the verification command that the trust path actually holds.

Apply HTTPS to nginx

Add an HTTPS server block on port 8443 to nginx. Set ssl_certificate to fullchain and ssl_certificate_key to server.key, and for ssl_protocols allow only TLSv1.2 TLSv1.3. The output of openssl s_client -connect 127.0.0.1:8443 -CAfile /root/ng/tls/ca.crt must show Verify return code: 0.

If the certificate and key are not a pair, nginx doesn't even start. Specify the protocols leaving out the deprecated versions.

HTTP → HTTPS redirect

Make all requests on port 8088 be redirected with a 301 to https://. The result of curl -s -o /dev/null -w '%{http_code} %{redirect_url}' http://127.0.0.1:8088/x must be 301, and the redirect URL must start with https:// and contain 8443.

return is clearer and faster than rewrite. Both the status code and the Location header must be right.

Expiry monitoring script

Create /root/ng/certcheck.sh. It takes two arguments (인증서파일 임계일수, the certificate file and the threshold in days) and ends with exit code 0 if the remaining validity is more than the threshold days, and with a non-zero exit code if expiry is within the threshold days. If the certificate file doesn't exist, it must also end with a non-zero exit code.

openssl x509 has an option that tells you through the exit code 'whether the remaining validity is N seconds or more'. If a monitoring script uses it, no date parsing is needed.