What’s up guys! Welcome (or welcome back) to the blog. In our last blog on Mastering SSL, we set up our internal Certificate Authority (CA) and covered the basics of PKI. That was part one of Phase 1, and everything in this article builds directly on that work. If you haven’t watched it yet, I recommend going back before continuing — your CA needs to be in place before you can follow along here.

In this part, we’re focusing on the server side. The goal is to generate a private key for the server, create a Certificate Signing Request (CSR), use our internal CA to sign it, and then install the signed certificate on both the server and client machines so that communication between them is fully encrypted.

Understanding the CSR

The CSR, or Certificate Signing Request, is a file you generate on the server. It contains your public key along with identifying details about the server such as its domain name and organization. It’s also digitally signed using your private key, which proves to the Certificate Authority that you own the matching private key. When the CA receives your CSR, it verifies the information and, if everything checks out, signs and issues a trusted certificate. Think of it as your server saying to the CA: “Here’s my public key, here’s who I am, and here’s proof I own the private key.”

Choosing the Type of Certificate

When you create a CSR, you can request either an application-specific certificate or a wildcard certificate. An application-specific certificate is issued for a single hostname, such as vault.example.com or proxmox.example.com. You can expand it to cover multiple hostnames using the Subject Alternative Name (SAN) field, but doing so means you must regenerate the certificate each time you add a new hostname. This also means you’re sharing one private key across multiple servers, which is less than ideal for security.

A wildcard certificate is more flexible. It covers any subdomain under your domain — for example, vault.example.com, proxmox.example.com, and files.example.com could all be secured by a single wildcard certificate. The trade-off is that you’re still using the same private key for all those services. For this lab, I’m using a wildcard certificate because it makes testing easier.

Setting Up the Lab Environment

To demonstrate this process, I’m using a Proxmox server as the main server, along with two client VMs — one running Windows and the other running Pop!_OS Linux. All three machines are on the same network.

Before we even generate keys, it’s important that the Proxmox server can be accessed using its fully qualified domain name (FQDN) instead of just an IP address. Certificates are tied to domain names, not IPs, so accessing the server by IP will cause a certificate mismatch.

Making Sure the Server Has an FQDN

Both client VMs must be pointed at our internal DNS server. On the DNS server, I’ve created an A record for the Proxmox server so it can be reached at proxmox.ktechlab.com. After restarting the DNS service and testing with a ping, I can now access the Proxmox web interface at https://proxmox.ktechlab.com:8006. This step is crucial because the certificate will be issued for the domain name, not the IP address.

Generating the Private Key

With DNS in place, it’s time to generate the private key. I’m using OpenSSL for this, as it’s the core tool behind most PKI operations. The command looks like this:

  • openssl genrsa -out ktechlab.key 2048

This private key must be kept secure at all times. It will be used to generate the CSR, and it already contains everything needed to create the public key.

Creating the CSR with a Config File

Rather than answering prompts each time, I prefer to create a config file that contains all the CSR details, including the SAN entries for additional hostnames or a wildcard.

[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
C = US
ST = YourState
L = YourCity
O = KTech Lab
CN = *.ktechlab.com

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = *.ktechlab.com

For a wildcard, the Common Name (CN) would be set to something like *.ktechlab.com, and the SAN section would also include any specific hostnames I want the certificate to cover. Using this config file with OpenSSL makes generating the CSR straightforward and repeatable. Once the config is ready, I run:

  • openssl req -new -key ktechlab.key -out ktechlab.csr -config ktechlab-wildcard.cnf

This produces the CSR, which we’ll now have the CA sign.

Signing the CSR with the Internal CA

I transfer the CSR to the CA server — scp works well for this — and then sign it using Step CA:

  • step ca sign ktechlab.csr ktechlab-wildcard.crt

The result is a signed certificate ready to install on the server.

Update Your ca.json Configuration for Certificate Duration

To issue TLS certificates with your desired duration using Step CA, you need to update the ca.json file located at:

  • /root/.step/config/ca.json
  • minTLSCertDuration: The minimum certificate lifetime allowed (e.g., 5 minutes)
  • maxTLSCertDuration: The maximum certificate lifetime allowed (e.g., 8760 hours = 1 year)
  • defaultTLSCertDuration: The default lifetime for issued certificates if none is specified

Make sure the authority.claims section reflects the certificate duration limits you want:

"authority": {
"claims": {
"minTLSCertDuration": "5m",
"maxTLSCertDuration": "8760h",
"defaultTLSCertDuration": "8760h"
}
}

Adjust these values based on your security and operational needs.

Optionally: Override Durations Per Provisioner

If you want to set custom durations for a specific provisioner, you can override the global values using this command:

step ca provisioner update Your-Chosen-Admin \
--x509-default-dur=8784h \
--x509-min-dur=5m \
--x509-max-dur=12000h

Installing the Certificate on Proxmox

At this point, we have two critical files: the private key we generated earlier and the signed certificate we just received from the CA. In Proxmox, I go to Datacenter → Certificates, paste in the private key and the full certificate chain (including intermediate certificates), save the changes, and restart Proxmox. From now on, Proxmox will present this signed certificate when accessed over HTTPS.

Distributing the Root CA to Clients

Because we’re using an internal CA, our clients won’t automatically trust its certificates. The root CA certificate must be installed on each client machine. On Windows, I import it into the Trusted Root Certification Authorities store, either at the system level or in the browser’s certificate settings. On Linux, I copy it into /usr/local/share/ca-certificates/ and run sudo update-ca-certificates. For browsers like Firefox, which don’t always use the system trust store, the CA must be imported directly into the browser’s settings.

Verifying the Secure Connection

With the CA installed, I can now visit https://proxmox.ktechlab.com:8006 from a client, check the certificate, and confirm that it chains back to our root CA. The padlock icon shows the connection is secure, and the certificate details confirm that the server is presenting the correct wildcard certificate.

Final Thoughts

In this part of Phase 1, we’ve gone from generating a server private key to creating a CSR, having it signed by our internal CA, and installing the signed certificate on both the server and clients. Along the way, we made sure to set up DNS properly, understood the difference between application-specific and wildcard certificates, and learned why distributing the root CA is essential for internal setups.

This completes Phase 1. In Phase 2, we’ll explore how to use publicly trusted certificates, such as those from Let’s Encrypt, while still keeping your services private and internal. That way we can avoid the hassle of manually installing the CA on every client while maintaining a secure, internal environment. As always— Stay geeking. Stay curious. Peace!

About the author

Quiet Engineer

A lifelong learner with a passion for tech and soccer. Koroma Tech is where I share my journey and help others along the way. I enjoy taking on challenges and see each one as a chance to learn and grow.

>