Showing posts with label SSL. Show all posts
Showing posts with label SSL. Show all posts

Sunday, September 20, 2026

Why HTTPS Works in Browser but Fails in Mobile Apps: Understanding SSL Certificate Chains

Why HTTPS Works in a Browser but Fails in a Mobile App

A few days ago, I came across an interesting issue in a UAT environment.

The API URL was working perfectly fine when accessed through a browser, but the same API was failing from a mobile application.

At first glance, it looked like a mobile application issue.

But it wasn't.

The actual problem was with the SSL certificate chain configured on the server.

This is one of those issues that can waste quite a bit of time because everything looks fine when you test it from a browser.

The confusing part

Let's say our API is:

https://api.example.com

When we opened the URL in a browser, there was no obvious SSL warning. HTTPS looked perfectly fine.

But the mobile application couldn't establish the connection.

That raised the usual questions:

  • Is something wrong with the API?

  • Is the mobile app using the wrong URL?

  • Is there a network issue?

  • Is the SSL certificate expired?

  • Is the backend rejecting the request?

The certificate itself was valid.

The problem was that the server wasn't presenting the complete certificate chain.

What is a certificate chain?

An SSL/TLS certificate isn't always a standalone certificate.

There is usually a chain of trust involved:

Your Domain Certificate
        ↓
Intermediate CA
        ↓
Root CA
        ↓
Client Trust Store

For example:

*.example.com
      ↓
Intermediate CA
      ↓
Root CA
      ↓
Trusted by the client

The domain certificate identifies the server.

The intermediate certificate provides the link between the server certificate and the trusted root.

That link is important.

Why can the browser still work?

This is probably the part that causes the most confusion.

A browser may already have the required intermediate certificate available, or it may be able to retrieve it when necessary.

So you can end up with a situation like this:

Browser
   ↓
Server Certificate
   ↓
Finds intermediate certificate
   ↓
Connection works

Another client may not behave the same way:

Mobile App
   ↓
Server Certificate
   ↓
Cannot build complete trust chain
   ↓
TLS validation fails

So the fact that Chrome can open your website doesn't necessarily prove that every HTTPS client will be able to establish the connection.

That's what makes certificate-chain issues particularly confusing.

Certificate vs Full Chain

When you purchase or renew an SSL certificate, your certificate provider may give you several files.

For example:

example.crt
example.key
example.ca-bundle

The .crt is the server/domain certificate.

The .key is the private key.

The .ca-bundle contains the CA certificates required to build the trust chain.

The private key stays separate.

For Nginx, you generally create a full-chain file containing the server certificate followed by the required intermediate certificates.

For example:

cat example.crt example.ca-bundle > fullchain.pem

The important thing is the order:

Server Certificate
        ↓
Intermediate Certificate
        ↓
Additional Intermediate Certificate

You shouldn't simply assume that every certificate in a CA bundle needs to be sent by the server. Inspect the bundle and follow the CA provider's recommended chain.

Don't just trust the file — verify it

One of the useful tools for troubleshooting SSL problems is OpenSSL.

You can inspect the certificates in a CA bundle with:

openssl crl2pkcs7 -nocrl \
  -certfile example.ca-bundle | \
  openssl pkcs7 -print_certs -noout

This helps you understand:

  • Who issued the certificate

  • Which intermediate CA is being used

  • Which root CA is involved

  • How the certificate hierarchy is structured

You can also verify the certificate chain:

openssl verify -show_chain \
  -CAfile example.ca-bundle \
  example.crt

A successful result should look something like:

example.crt: OK

That's much better than simply assuming the certificate is correct because the browser doesn't show a warning.

Verify the certificate and private key

Another check I always recommend is making sure the certificate and private key actually belong together.

For RSA certificates, you can compare their modulus hashes:

openssl x509 -noout -modulus \
  -in example.crt | openssl sha256

And:

openssl rsa -noout -modulus \
  -in example.key | openssl sha256

The hashes should match.

Configure Nginx

Once the correct full chain has been prepared, Nginx should point to it.

For example:

server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.key;

    ...
}

The important part is:

ssl_certificate /etc/nginx/ssl/fullchain.pem;

rather than pointing only to the domain certificate.

After making the change:

sudo nginx -t

If everything looks good:

sudo systemctl reload nginx

But how do you know what the server is actually sending?

This is another important lesson.

Checking the certificate files on the server isn't enough.

You need to check what the server is actually presenting to clients.

OpenSSL can do this:

openssl s_client \
  -connect api.example.com:443 \
  -servername api.example.com \
  -showcerts </dev/null

Look for:

Certificate chain

You should see the server certificate followed by the required intermediate certificates.

More importantly, look for:

Verification: OK

and:

Verify return code: 0 (ok)

This gives you much stronger evidence that the live HTTPS endpoint is presenting a usable certificate chain.

What about mobile applications?

It's important not to conclude that every SSL issue in a mobile application is a server issue.

Mobile applications can have their own TLS-related problems, including:

  • Certificate pinning

  • Custom trust stores

  • Network security configuration

  • Outdated device CA certificates

  • TLS version compatibility

  • Incorrect hostname validation

  • Proxy configuration

So if the server chain is correct and the mobile application still fails, the next step is to investigate the application's TLS configuration.

The key is to check both sides instead of immediately assuming one is responsible.

A simple troubleshooting checklist

When an HTTPS API works in a browser but fails from a mobile application, I usually check these in order:

✓ Certificate expiry
✓ Domain name / SAN
✓ Certificate and private key match
✓ Intermediate certificates
✓ Certificate chain
✓ What the server actually sends
✓ TLS configuration
✓ Mobile certificate pinning
✓ Mobile network security configuration

A few OpenSSL commands can answer most of the server-side questions very quickly.

The bigger lesson

SSL problems are often misunderstood because we tend to think about HTTPS as:

Certificate + Private Key = HTTPS

In reality, there is a trust relationship involved:

                Server
                  │
                  ▼
          Domain Certificate
                  │
                  ▼
           Intermediate CA
                  │
                  ▼
              Root CA
                  │
                  ▼
          Client Trust Store

If the server doesn't provide the certificates required to build that chain, different clients may behave differently.

That's why an HTTPS endpoint should be tested from the actual clients that consume it, not just from a browser.

A valid SSL certificate doesn't necessarily mean that your HTTPS server is correctly configured.

When an API works in a browser but fails in a mobile application, don't immediately start debugging the application code.

First, check the certificate chain.

Check what certificates the server is actually sending.

And verify the chain using OpenSSL.

A simple command like this can often reveal the problem:

openssl s_client \
  -connect api.example.com:443 \
  -servername api.example.com \
  -showcerts </dev/null

The lesson is simple:

Don't just check whether the certificate is valid. Check whether the complete trust chain is being served correctly.

Sometimes a small SSL configuration issue on the server can look like a completely unrelated mobile application problem.

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.

Monday, January 15, 2018

.htaccess

Hello,

Recently I configured SSL on Amazon EC2 instance running Apache server. In this blog I am going to explain the procedure step by step.

Step 1 : Generate CSR and submit to CA authority for verification

To generate CSR login to your server with SSH and use following command.

openssl req -new -newkey rsa:2048 -nodes -keyout YourDomain.key -out YourDomain.csr

Once you execute this command it will ask for certain inputs and based on it it will generate csr file. That you have to submit to CA authority and generate

Step 2 : Edit Inbound rules on your EC2 Instance and allow HTTPS

Login to Amazon AWS console and go to EC2 dashboard. Click on instance and edit security rules. Select HTTPs and allow it from anywhere.


Step 3 : Upload your SSL certificate and key file to EC2 using SSH

 You can use SCP command to upload crt file and pem key file to server.

scp -i "YourKey.pem" "Cert.crt" ubuntu@YourIP:/home/ubuntu/Cert.crt
scp -i "YourKey.pem" "Key.key" ubuntu@YourIP:/home/ubuntu/Key.key

Step 4 : Edit Default SSL config file and Add Certificate In File

Go to  cd /etc/apache2/sites-available

Update default-ssl.conf file by using command

sudo nano default-ssl.conf

Add following lines in file

SSLEngine on
SSLCertificateFile      /home/ubuntu/Cert.crt
SSLCertificateKeyFile /home/ubuntu/Key.key

Save the file.

Step 5 : Enable SSL mode in Apache and set config file to default-ssl.conf

Now we have ssl config file set, we have to enable SSL mode in apache and set config file.

Use following command

sudo a2enmod ssl
sudo a2ensite default-ssl.conf
sudo service apache2 reload
sudo service apache2 restart

That's it and now you can access your web app on HTTPS. Hope this helps you.