Note: To keep this guide clear and easy to follow, I’ve divided it into two parts. The first part covers the overall process and goals—the 'why' behind each step. The second part, found at the end of this article, is a Command Reference Guide with all the commands used in the video. This way, we can focus on the big picture first, then quickly reference the practical commands when it’s time to build. Keep in mind that specific commands and configurations may vary depending on your operating system, its version, and your environment.
What's up guys! Welcome (or welcome back) to the blog. In the last couple of videos in our SSL series, we set up our own internal Certificate Authority (CA), explored how Public Key Infrastructure (PKI) works, and even generated and pushed certificates to some of the services on our internal network. That was Phase 1, designed for those who want more control over their certificate process. Now, we're moving on to Phase 2, which is, by far, the quickest and easiest way to secure our internal services using a public CA like Let's Encrypt.
Our game plan for this video is straightforward. First, we'll acquire a domain name. Then, we'll point that domain to Cloudflare. Next, we'll set up an internal DNS zone that mirrors our external domain, allowing us to use the same domain name internally. After that, we'll install a reverse proxy and use a tool called Certbot with the Cloudflare DNS plugin to verify our domain and automatically generate a Let's Encrypt wildcard certificate. The proxy will handle SSL termination. This setup will secure all of our services behind the proxy and cover them with our wildcard certificate.
Before we start, here's what we'll need:
- A domain name: This usually costs around $12 a year. While "cheap" is subjective, it's a great investment for learning and building, both for our current internal setup and for when we eventually make our applications public-facing.
- A working DNS server: If you don't have one, I have a video on how to set that up, and I’ll link to it in the description.
- A free Cloudflare account: We'll only need the free tier for this project, and it's a quick and easy sign-up process.
Public vs. Internal CA: A Quick Look
Before we jump in, let's briefly discuss why we're using a public CA instead of sticking with the internal CA we built. A public CA works a lot like our internal one, but the key difference is that it's globally trusted. As long as we can prove we own a domain, we can request a certificate from a public CA.
The biggest benefit? We don't have to manually push our root certificate to every client. Remember with our internal CA, we had to create the certificate and then distribute our root certificate to every device so it would be recognized. With a public CA like Let's Encrypt, that problem goes away.
Their certificate chain links back to a root CA that is already built into the trust store for virtually every operating system, browser, and app. This means that when our server presents a certificate signed by Let's Encrypt, the client automatically trusts it out of the box.
This is a huge time-saver. It means we don't have to manage our own CA or worry about distributing our certificates across our environment; we just focus on the server side of things. Typically, we'd use a public CA for front-facing services so that traffic from the outside world is encrypted and trusted.
In our case, we're pulling certificates from Let's Encrypt but keeping everything internal for now. The goal is to secure our services without the headache of manual certificate management. And the best part is that Let's Encrypt is free and automated.
Getting Our Domain and Connecting to Cloudflare
To get a certificate from a public CA, we'll need a domain and a way to prove that it belongs to us. Our first step is to buy a domain and connect it to Cloudflare. We'll also get an API token from Cloudflare so our tools can automatically communicate with it to request certificates from Let's Encrypt.
We're using Namecheap as our domain registrar, but you're welcome to use any service you like, such as GoDaddy. What matters is that you own the domain and can prove it.
After our purchase, we'll head to Cloudflare's website. Cloudflare is a service that acts as a middleman between our domain and the internet, providing DNS hosting, security features, and SSL management. We'll create a free account, and then it will ask us to enter our new domain.
The next step is to update our name servers. Cloudflare will give us two name servers, and we'll need to go back to our domain registrar (in our case, Namecheap) and change the DNS settings from Namecheap Basic DNS to Custom DNS. We'll then paste the two Cloudflare name servers into those fields.
This delegates our DNS management to Cloudflare, which is what we want because it gives us the ability to automate DNS changes through their API, a necessary step for our automated certificate renewal.
Once the name servers are updated (this can take a few minutes to an hour), we'll go back to Cloudflare. We'll navigate to our profile, select "API Tokens," and create a new token. We'll use the "Zone DNS" template, select the "Edit" permission, and apply it to our specific domain. It's crucial to save this token somewhere safe as we'll need it for our next step and won't be able to view it again after we close the window.
Installing and Configuring Our Reverse Proxy
Now that we have our domain and API key, we need to set up our proxy server. We'll install a tool called Certbot and its Cloudflare DNS plugin. Certbot is a free, open-source tool that helps us automatically obtain and renew SSL/TLS certificates from Let's Encrypt. The Certbot DNS Cloudflare plugin allows us to use Cloudflare's DNS to prove domain ownership.
We'll install a proxy server and configure it manually. While there are great options like NGINX Proxy Manager with a user-friendly GUI, we'll be taking the hands-on approach by installing NGINX directly on an LXC container. This allows us to understand the underlying configuration files.
A proxy server is a middleman between a client and a backend server. It simplifies access, adds a layer of security, and handles SSL termination, which means it's the endpoint for the encrypted connection. It decrypts the traffic and then forwards it to the backend server. We'll be using NGINX, and once we've installed it and updated our system, we'll create a new DNS entry for our proxy server so our internal network can resolve its hostname.
Next, we'll install Certbot and the Cloudflare DNS plugin using our system's package manager. After that, we'll create a folder to store the Cloudflare API key we generated earlier. We'll then create a file inside this folder, paste in our API key, and set secure permissions to ensure only the root user can read it.
With Certbot and our API key in place, we can run the command to request our wildcard certificate. This command tells Certbot to use the Cloudflare DNS plugin to validate our domain and get a wildcard certificate for all subdomains. After a short wait, Certbot will successfully retrieve and store the certificates on our server. We'll then set up a system timer for Certbot to automatically renew our certificate before it expires, ensuring our SSL stays valid without any manual intervention.
Now for the final crucial step: setting up our DNS server. DNS is the backbone of this setup. We have two main ways to do this, depending on our environment. If our new public domain matches our internal domain, we can create two A records for each service. For example, we'd have proxmox.domain.com pointing to our proxy server and proxmox-server.domain.com pointing directly to the backend server. This separation is helpful for troubleshooting, allowing us to ping the server directly.
However, since our external domain is different from our internal one, we'll set up a new internal DNS zone that mirrors our external domain. This keeps things clean and separate. We'll add this new zone to our DNS server's configuration and then create a wildcard A record within that zone. This wildcard record will point any subdomain of our new domain (e.g., proxmox.quietengineerlog.com, vault.quietengineerlog.com) directly to our proxy server's IP address. This way, all traffic for our services will hit the proxy first.
Finalizing the NGINX Proxy Configuration
We've got everything ready: our DNS is configured to point traffic to the proxy, and we have our wildcard certificate. The only thing left is to tell the proxy what to do with that traffic.
We'll navigate to the NGINX configuration directory. We'll create a new configuration file for our service in the sites-available directory. Inside this file, we'll tell NGINX to:
- Listen on port 443 (the secure port).
- Use the wildcard certificate we generated.
- Redirect traffic to our backend server, which we'll specify by its internal FQDN or IP address and port.
- Force an SSL redirect for any HTTP traffic, ensuring all connections are secure.
We'll also specify some optional settings, such as passing header information and keeping the connection alive. This configuration file will be our template for all future services. Once the config file is complete, we'll create a symlink to it in the sites-enabled directory and restart the NGINX service to apply our changes.
With NGINX restarted, our setup is complete! When we now navigate to proxmox.quietengineerlog.com, it will hit our proxy server, which performs SSL termination using our Let's Encrypt certificate and then redirects us to our Proxmox backend. The connection is secure, and we didn't have to manually distribute any certificates. Our services are protected, and the process is automated.
Command Reference Guide 
This section provides the specific commands used throughout the guide. Remember to substitute the placeholder values.
Create the LXC container: (Proxmox)
- Cores: 2 (You may use 1 unless your proxy will experience heavy usage)
- Memory 512
Update the container:
- apt update && apt upgrade -y
Install NGINX:
- apt install nginx -y
Install Certbot and the Cloudflare plugin:
- apt install certbot python3-certbot-dns-cloudflare -y
Create the directory for your API key:
- mkdir -p /etc/letsencrypt/
Create and edit the Cloudflare credentials file:
- nano /etc/letsencrypt/cloudflare.ini
- Paste the following into the file, replacing the placeholder with your token
- dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
- Secure the credentials file:
- chmod 600 /etc/letsencrypt/cloudflare.ini
- Paste the following into the file, replacing the placeholder with your token
Enable Certbot for automatic renewal:
- systemctl enable --now certbot.timer
Edit the DNS server configuration file:
- nano /etc/bind/named.conf.local
Add the following zone, replacing the domain name and file path with your own:
zone "quietengineerlog.com" {
type master; file "/etc/bind/db.quietengineerlog.com";
};
Copy the empty DNS zone file:
- cp /etc/bind/db.empty /etc/bind/db.quietengineerlog.com
Edit the new DNS zone file:
- nano /etc/bind/db.quietengineerlog.com
Add the following records, replacing the domain name and IP addresses with your own:

Restart the BIND9 service:
- systemctl restart bind9
- Obtain the wildcard certificate:
- sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
--dns-cloudflare-propagation-seconds 60 \
-d quietengineerlog.com -d (replace with your domain) '*.quietengineerlog.com(replace with your domain)' \
--non-interactive --agree-tos -m admin@quietengineerlog.com
- sudo certbot certonly \
- Cert/key Location
- `ssl_certificate /etc/letsencrypt/live/(your domain)/fullchain.pem;
- ssl_certificate_key /etc/letsencrypt/live/(your domain)/privkey.pem;
- Navigate to the NGINX configuration directory:
- cd /etc/nginx/sites-available/
- Create a new configuration file for your service:
- nano proxmox.conf
- Paste and edit the following configuration:
server {
listen 443 ssl;
server_name proxmox.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
location / {
proxy_pass https://proxmox.yourdomain.com:8006;
# verify backend cert instead of ignoring
proxy_ssl_verify off;
proxy_ssl_server_name off;
proxy_ssl_name proxmox.ktechlab.com;
proxy_ssl_trusted_certificate /etc/nginx/ssl/proxmox-ca.pem;
proxy_ssl_verify_depth 2;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name proxmox.yourdomain.com.com;
return 301 https://$host$request_uri;
}
- Create the symlink to enable the configuration:
- ln -s /etc/nginx/sites-available/proxmox.conf /etc/nginx/sites-enabled/
- Test the NGINX configuration for errors:
- nginx -t
- Restart NGINX to apply changes:
- systemctl restart nginx
This is a wrap for our SSL series for now. We've covered how to set up our own internal CA and how to use a public CA when needed. We'll tackle more topics as we continue to build out our lab environment. We are learning and building so many things, and we want to share that journey. Don't forget to subscribe and turn on notifications below!
As always, stay geeking, stay curious, and I'll catch you on the next one. Peace.