Showing posts with label Troubleshooting. Show all posts
Showing posts with label Troubleshooting. Show all posts

Sunday, August 30, 2026

How to Set Up Let’s Encrypt SSL Certificate with Nginx

If we are hosting a web application on a Linux server, enabling HTTPS is one of the first things we should take care of before exposing the application to users.

One of the easiest ways to do this is by using Let’s Encrypt. It provides free SSL/TLS certificates, and with Certbot we can automate both certificate installation and renewal.

In this article, we will go step by step through the process of configuring a Let’s Encrypt certificate with Nginx. We will also look at certificate renewal and some common issues that can prevent the certificate from being generated or renewed successfully.

What We Are Going to Set Up

We will configure Nginx to serve our application over HTTPS using a certificate issued by Let’s Encrypt.

The setup will include:

  • A domain pointing to our server

  • Nginx configured as the web server or reverse proxy

  • A Let’s Encrypt SSL certificate

  • HTTPS access on port 443

  • HTTP to HTTPS redirection

  • Automatic certificate renewal

Prerequisites

For this guide, we will assume that:

  • We have a Linux server.

  • Ubuntu is being used as the operating system.

  • Nginx is installed or can be installed.

  • We have a domain name.

  • We have access to the DNS configuration for the domain.

  • We have sudo access to the server.

  • Ports 80 and 443 are accessible from the internet.

For the examples below, we will use:

Domain: example.com
Server IP: 203.0.113.10

Replace these values with the actual domain and server details.

Step 1: Point the Domain to the Server

Before requesting an SSL certificate, our domain needs to resolve to the server where Nginx is running.

In the DNS configuration, create an A record.

Type: A
Name: @
Value: 203.0.113.10

If we also want to support www.example.com, we can create another record:

Type: A
Name: www
Value: 203.0.113.10

Depending on the DNS provider, the interface will look different, but the concept remains the same.

We should verify that DNS is resolving correctly before moving forward.

From the server or our local machine, run:

nslookup example.com

or:

dig example.com

The returned IP address should match our server's public IP.

If DNS is not resolving correctly, there is no point proceeding with Certbot yet. Let's Encrypt needs to verify that we control the domain.

Step 2: Install Nginx

If Nginx isn't already installed, we can install it using:

sudo apt update
sudo apt install nginx -y

Once the installation is complete, check the service:

sudo systemctl status nginx

We should see that the service is running.

We can also verify the configuration:

sudo nginx -t

A successful configuration check should return something similar to:

syntax is ok
test is successful

Now open the domain in a browser:

http://example.com

At this stage, we should get the Nginx default page or our application, depending on how Nginx has been configured.

Step 3: Configure Nginx for the Domain

Before requesting the certificate, we should configure a server block for our domain.

Create a configuration file:

sudo nano /etc/nginx/sites-available/example.com

Add the following:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;

        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;
    }
}

The proxy_pass value should point to the application running behind Nginx.

For example, if our application is running on port 3000:

proxy_pass http://127.0.0.1:3000;

If the application is running on another server, we can use that server's address instead.

Step 4: Enable the Nginx Configuration

Create a symbolic link to enable the configuration:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Then test the configuration:

sudo nginx -t

If everything looks correct, reload Nginx:

sudo systemctl reload nginx

Now verify that the domain is accessible:

http://example.com

We should make sure the application works correctly over HTTP before moving to HTTPS.

Step 5: Install Certbot

Now we can install Certbot and the Nginx plugin.

sudo apt update
sudo apt install certbot python3-certbot-nginx -y

Verify the installation:

certbot --version

We should get the installed Certbot version as the output.

Certbot will communicate with Let's Encrypt, request the certificate and configure Nginx for HTTPS.

Step 6: Request the Let's Encrypt Certificate

Now we can request the certificate.

Run:

sudo certbot --nginx -d example.com -d www.example.com

Certbot will ask for some information, including an email address and agreement to the terms of service.

It will then communicate with Let's Encrypt and perform domain validation.

If the validation succeeds, Certbot will obtain the certificate and update the Nginx configuration.

One of the advantages of using the Nginx plugin is that we don't have to manually copy certificate paths into the Nginx configuration.

Step 7: Redirect HTTP to HTTPS

During the Certbot setup, we may be asked whether HTTP traffic should be redirected to HTTPS.

Choose the redirect option if we want all HTTP traffic to automatically use HTTPS.

After enabling the redirect, users visiting:

http://example.com

will automatically be redirected to:

https://example.com

This ensures that users always access the application through an encrypted connection.

Step 8: Verify the HTTPS Configuration

Open the following URL in a browser:

https://example.com

The browser should show the secure connection indicator.

We can also check the certificate from the command line:

openssl s_client -connect example.com:443 -servername example.com

This provides information about the certificate and TLS connection.

We can also check the Nginx configuration:

sudo nginx -t

If everything is configured correctly, Nginx should report:

syntax is ok
test is successful

Step 9: Check the Certificate

Certbot provides a convenient command to check the certificates currently installed on the server.

sudo certbot certificates

Example output:

Certificate Name: example.com
Domains: example.com www.example.com
Expiry Date: ...
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem

Certbot normally stores certificates under:

/etc/letsencrypt/

We generally shouldn't manually modify files inside this directory because Certbot manages the certificate lifecycle.

Step 10: Configure Automatic Renewal

Let's Encrypt certificates are short-lived and need to be renewed regularly.

The good part is that Certbot can handle renewal automatically.

First, check whether the Certbot renewal timer is active:

sudo systemctl status certbot.timer

We can also check the timers on the system:

systemctl list-timers | grep certbot

If the timer is configured correctly, Certbot will periodically check whether the certificate needs renewal.

Step 11: Test Certificate Renewal

We shouldn't wait until the certificate is about to expire to find out that automatic renewal doesn't work.

We can perform a dry run:

sudo certbot renew --dry-run

This performs a simulated renewal process without replacing the existing certificate.

A successful test should indicate that the renewal simulation completed successfully.

This is something worth testing after the initial setup and whenever we make significant changes to the Nginx or DNS configuration.

Common Problems

The installation itself is usually straightforward. Most problems occur during domain validation, Nginx configuration or certificate renewal.

Problem 1: Domain Doesn't Point to the Server

If Certbot reports that domain validation failed, the first thing we should check is DNS.

Run:

dig example.com

Make sure the returned IP address points to the correct server.

If DNS was changed recently, we may also need to wait for the DNS changes to propagate.

Problem 2: Port 80 Is Not Accessible

Let's Encrypt commonly needs to reach the server through HTTP during certificate validation.

Make sure port 80 is open.

On Ubuntu with UFW:

sudo ufw status

If required:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

If the server is running in a cloud environment, we also need to check the cloud firewall or security group.

Opening the port in UFW isn't enough if the cloud firewall is blocking the traffic.

Problem 3: Nginx Configuration Test Fails

If this command fails:

sudo nginx -t

we should fix the Nginx configuration before running Certbot.

The error message usually contains the configuration file and line number where the problem exists.

We can inspect the complete configuration using:

sudo nginx -T

This prints the complete Nginx configuration currently being loaded.

Problem 4: Too Many Redirects

After enabling HTTPS, we may sometimes encounter a redirect loop.

For example, the browser keeps switching between HTTP and HTTPS.

This usually happens when there is another reverse proxy or load balancer in front of Nginx and the original protocol isn't being handled correctly.

We should check:

  • Nginx redirect rules

  • Reverse proxy configuration

  • Load balancer configuration

  • X-Forwarded-Proto headers

We should avoid adding multiple layers of HTTPS redirects without understanding how traffic flows through the infrastructure.

Problem 5: Certificate Renewal Fails

If automatic renewal fails, first check the existing certificates:

sudo certbot certificates

Then test renewal manually:

sudo certbot renew --dry-run

Check the Certbot logs if the problem isn't obvious:

sudo ls /var/log/letsencrypt/

The logs usually provide enough information to identify whether the issue is DNS, Nginx, port accessibility or certificate validation.

Useful Commands

Here are some commands we can keep handy when troubleshooting a Let's Encrypt and Nginx setup.

Check Nginx status:

sudo systemctl status nginx

Test Nginx configuration:

sudo nginx -t

Reload Nginx:

sudo systemctl reload nginx

Check certificates:

sudo certbot certificates

Test renewal:

sudo certbot renew --dry-run

Check Certbot timer:

systemctl list-timers | grep certbot

Check HTTPS certificate:

openssl s_client -connect example.com:443 -servername example.com

Check DNS:

dig example.com

A Few Things We Should Keep in Mind

Getting the certificate is only one part of enabling HTTPS.

We should also make sure that:

  • HTTP redirects to HTTPS.

  • Port 443 is accessible.

  • The certificate covers all required domains.

  • Automatic renewal is working.

  • Nginx configuration remains valid.

  • Application URLs use HTTPS where required.

  • Mixed-content issues are not introduced in the application.

HTTPS should be treated as part of the application's infrastructure rather than simply a certificate that we install once and forget about.

Saturday, July 25, 2026

Why Your Kubernetes Pod Keeps Restarting: A Complete Debugging Guide

If you've been working with Kubernetes for any length of time, you've probably encountered a pod that refuses to stay running. One moment it's starting successfully, and the next moment it's restarting. After a few attempts, Kubernetes reports a CrashLoopBackOff status, leaving many engineers wondering what went wrong.

The first reaction is often to delete the pod and hope Kubernetes creates a healthy replacement. While this may appear to solve the problem temporarily, it rarely fixes the actual issue. More importantly, deleting the pod too quickly can remove valuable information that would have helped identify the root cause.

The good news is that Kubernetes almost always tells us why a pod is restarting. We simply need to know where to look and follow a structured troubleshooting approach instead of making assumptions.

In this article, we will walk through the exact process we can use to diagnose and resolve Kubernetes pods that keep restarting.

Step 1: Check the Pod Status

Whenever a pod starts restarting, our first objective is to understand what Kubernetes already knows about it.

The first command we should run is:

kubectl get pods

Example output:

NAME                    READY   STATUS             RESTARTS   AGE
payment-api-65d8fd      0/1     CrashLoopBackOff   12         18m

Pay close attention to these three columns:

  • STATUS

  • RESTARTS

  • AGE

A restart count of 10 or 20 immediately tells us the application has been crashing repeatedly and Kubernetes has been trying to recover it.

Some common pod statuses include:

StatusMeaning
RunningApplication is healthy
PendingWaiting to be scheduled
CrashLoopBackOffContainer keeps crashing after startup
ImagePullBackOffUnable to pull the container image
ErrImagePullImage download failed
CompletedJob completed successfully
TerminatingPod is shutting down

Once we've confirmed the pod is restarting, the next step is to gather more information.

Step 2: Describe the Pod

One of the most useful commands in Kubernetes troubleshooting is:

kubectl describe pod <pod-name>

Many engineers immediately jump to application logs, but kubectl describe provides far more context.

It includes information such as:

  • Current container state

  • Previous container state

  • Restart count

  • Mounted volumes

  • Environment variables

  • Probe failures

  • Scheduling information

  • Events

The Events section at the bottom deserves special attention because Kubernetes records everything significant that happened to the pod.

For example:

Warning  Unhealthy
Readiness probe failed

Warning  BackOff
Back-off restarting failed container

In many situations, the Events section already points us in the right direction before we even inspect the application logs.

Step 3: Read the Application Logs

Once we've reviewed the pod details, it's time to inspect the application logs.

kubectl logs <pod-name>

If the pod has already restarted, don't forget to check the logs from the previous container instance:

kubectl logs <pod-name> --previous

This is one of the most overlooked commands in Kubernetes.

When a container crashes and restarts, the current logs may only show startup messages. The actual exception often exists only in the previous container's logs. We've solved many production issues simply by checking kubectl logs --previous.

Now that we've gathered the basic information, we can start identifying the actual reason behind the restarts.

Common Causes of Restarting Pods

In most production environments, restarting pods usually fall into one of the following categories.

CrashLoopBackOff

This is probably the most common Kubernetes error.

A CrashLoopBackOff doesn't tell us what failed—it simply tells us Kubernetes keeps restarting the container because it exits shortly after starting.

Common reasons include:

  • Application exceptions

  • Missing environment variables

  • Database connection failures

  • Invalid configuration

  • Missing ConfigMaps

  • Missing Secrets

  • Startup script failures

For example:

Error: Database connection refused

The application exits.

Kubernetes restarts it.

The application crashes again.

Eventually Kubernetes delays each restart attempt and reports CrashLoopBackOff.

It's important to remember that CrashLoopBackOff is a symptom, not the root cause.

The real reason is almost always available in the application logs.

OOMKilled

Another common reason for pod restarts is an out-of-memory condition.

If the application consumes more memory than allowed, the Linux kernel terminates the container.

We can verify this using:

kubectl describe pod <pod-name>

Look for something similar to:

Last State:
Terminated

Reason:
OOMKilled

Typical causes include:

  • Memory leaks

  • Processing large files

  • Image or PDF conversion

  • Loading large datasets into memory

  • Incorrect resource limits

Many teams immediately increase the memory limit and redeploy the application.

While that may stop the restarts temporarily, it's always worth understanding why the application is consuming excessive memory before increasing resources.

Readiness Probe Failures

A readiness probe tells Kubernetes whether the application is ready to receive traffic.

Example:

readinessProbe:
  httpGet:
    path: /health
    port: 8080

If the readiness probe fails:

  • The pod continues running.

  • Kubernetes stops routing traffic to it.

Common reasons include:

  • Wrong endpoint

  • Wrong port

  • Slow application startup

  • Database not yet available

  • Dependent services still starting

Readiness failures usually indicate that the application isn't fully initialised yet.

Liveness Probe Failures

Unlike readiness probes, liveness probes determine whether the application is still healthy.

If a liveness probe keeps failing, Kubernetes assumes the application is unhealthy and restarts it automatically.

Example:

livenessProbe:
  httpGet:
    path: /health
    port: 8080

One common mistake is configuring the liveness probe too aggressively.

For example, if an application requires 60 seconds to initialise but the liveness probe starts after only 15 seconds, Kubernetes may repeatedly kill the container before it has a chance to finish starting.

ImagePullBackOff

Sometimes the container never starts because Kubernetes cannot download the container image.

Possible reasons include:

  • Incorrect image name

  • Wrong image tag

  • Private registry authentication failure

  • Registry connectivity issues

  • Image does not exist

The quickest way to investigate is:

kubectl describe pod <pod-name>

Again, the Events section usually contains the exact reason for the image pull failure.

Configuration Problems

Configuration errors are another common cause of restarting pods.

Examples include:

  • Missing ConfigMaps

  • Missing Secrets

  • Incorrect environment variables

  • Invalid file paths

  • Missing certificates

  • Invalid application configuration

Even a simple typo in an environment variable can prevent an application from starting successfully.

Step 4: Check Resource Usage

Sometimes the problem isn't the application itself.

The Kubernetes node may be under heavy resource pressure.

We can check resource usage using:

kubectl top pod

kubectl top node

These commands help identify:

  • High CPU usage

  • High memory usage

  • Resource spikes

  • Node pressure

If these commands don't work, ensure the Kubernetes Metrics Server is installed.

Step 5: Review Cluster Events

Cluster events often provide additional context that application logs cannot.

Run:

kubectl get events --sort-by=.metadata.creationTimestamp

Look for messages related to:

  • Failed scheduling

  • Failed mounts

  • Failed image pulls

  • Probe failures

  • Resource exhaustion

Events frequently tell the complete story of what happened before the pod entered its restart cycle.

Step 6: Review Recent Deployments

If the application was working yesterday but started restarting after a deployment, don't ignore the possibility that the latest release introduced the issue.

Review the deployment history:

kubectl rollout history deployment payment-api

If required, roll back to the previous version:

kubectl rollout undo deployment payment-api

Rolling back isn't the final solution, but it can restore service quickly while we continue investigating the root cause.

A Simple Troubleshooting Checklist

Whenever we encounter a restarting pod, following a consistent process saves time and avoids unnecessary guesswork.

  1. Check the pod status.

  2. Describe the pod.

  3. Review the current logs.

  4. Review the previous logs.

  5. Check the Events section.

  6. Verify CPU and memory usage.

  7. Inspect readiness and liveness probes.

  8. Check for image pull errors.

  9. Review recent deployments.

Following the same sequence every time helps us diagnose problems much faster.

Common Mistakes

Over the years, we've seen the same mistakes repeated in many Kubernetes environments.

  • Deleting the pod before collecting logs.

  • Ignoring the Events section.

  • Increasing memory without understanding the root cause.

  • Restarting deployments repeatedly instead of investigating.

  • Forgetting to use kubectl logs --previous.

  • Assuming Kubernetes is the problem when the application itself is crashing.

Avoiding these mistakes can significantly reduce troubleshooting time.

Kubernetes doesn't restart containers without a reason. Every restart is a symptom of an underlying issue, whether it's an application crash, resource exhaustion, configuration error, failed health check or infrastructure problem.

The goal isn't to memorise dozens of Kubernetes commands. Instead, we should develop a structured troubleshooting approach that helps us identify the root cause quickly and consistently.

The next time you see a pod stuck in CrashLoopBackOff, resist the temptation to delete it immediately. Start by checking the pod status, describing the pod, reviewing the logs and inspecting the Events section. In most cases, Kubernetes has already provided enough information to lead us to the solution.