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!