HomeContact

How I Fixed SSL Certificate Issues on My Website A Complete Guide

By Shady Nagy
Published in Linux
February 11, 2026
8 min read
How I Fixed SSL Certificate Issues on My Website A Complete Guide

Table Of Contents

01
The Problem That Started It All
02
Understanding What Went Wrong
03
Solution #1: Fixing the Missing Intermediate Certificate
04
Solution #2: Forcing HTTPS Redirects
05
Complete Diagnostic Commands
06
Final Configuration Files
07
The Results: Everything Fixed! ✅
08
Questions & Answers
09
Useful Online SSL Testing Tools
10
Key Takeaways
11
Final Thoughts
12
Feedback and Questions

The Problem That Started It All

Picture this: I had just installed an SSL certificate on my website, shadynagy.com. Everything looked great on my end—the certificate was valid, issued by Sectigo, and had plenty of time before expiration (265 days!). But when I ran a quick SSL test at WhyNoPadlock.com, I got hit with some disappointing red X marks:

Force HTTPS: Not forcing the use of SSL
Invalid Intermediate: Missing intermediate (bundle) certificate

My heart sank. What good is an SSL certificate if it’s not working properly? My visitors might see security warnings, search engines might rank me lower, and worst of all—my site could look unprofessional.

But here’s the good news: I fixed everything, and I’m going to show you exactly how I did it, step by step.

Understanding What Went Wrong

Before diving into solutions, let’s understand what these errors actually mean:

Problem #1: Missing Intermediate Certificate

What’s an intermediate certificate?

Think of SSL certificates like a chain of trust:

  • Your browser trusts certain “root” certificate authorities (like Sectigo)
  • These authorities issue “intermediate” certificates to sub-authorities
  • These sub-authorities issue your website’s certificate

Your website needs to send BOTH your certificate AND the intermediate certificate to browsers. Without the intermediate, the browser can’t verify the chain of trust.

Real-world impact:

  • Some browsers show security warnings
  • Mobile devices often fail to connect
  • Your site looks unsafe to visitors

Problem #2: No HTTP to HTTPS Redirect

What does this mean?

When someone types http://shadynagy.com (without the ‘s’), they should automatically be redirected to https://shadynagy.com. Without this redirect:

  • Visitors can access your site over insecure HTTP
  • Search engines see duplicate content (HTTP and HTTPS versions)
  • You lose the SEO benefits of HTTPS

Solution #1: Fixing the Missing Intermediate Certificate

Step 1: Understanding the Root Cause

I discovered my nginx configuration was using the wrong directive. Here’s what I had:

# ❌ WRONG CONFIGURATION
ssl_certificate /etc/nginx/ssl/shadynagy.com.crt;
ssl_trusted_certificate /etc/nginx/ssl/shadynagy.com.ca-bundle;

The problem: ssl_trusted_certificate is for OCSP stapling (a performance feature), NOT for sending the certificate chain to browsers!

Step 2: Creating the Full Chain Certificate

The solution is to create a “fullchain” certificate that combines your certificate with the intermediate certificate.

Command:

cat /etc/nginx/ssl/shadynagy.com.crt /etc/nginx/ssl/shadynagy.com.ca-bundle > /etc/nginx/ssl/shadynagy.com-fullchain.crt

What this does:

  • cat - Concatenates (combines) files
  • First file: Your website’s certificate
  • Second file: The intermediate CA bundle
  • > - Outputs to a new file called “fullchain”

Example explanation: Imagine you have two puzzle pieces:

  1. Your certificate (Piece A)
  2. The intermediate certificate (Piece B)

You’re gluing them together to create one complete puzzle that browsers can understand.

Step 3: Updating Nginx Configuration

Open your SSL configuration file:

nano /etc/nginx/conf.d/shadynagy.com-ssl.conf

Update to this:

server {
listen 443 ssl http2; # Port 443 with SSL and HTTP/2
# ✅ CORRECT - Use the fullchain certificate
ssl_certificate /etc/nginx/ssl/shadynagy.com-fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/shadynagy.com.key;
# Optional: Keep for OCSP stapling (performance boost)
ssl_trusted_certificate /etc/nginx/ssl/shadynagy.com.ca-bundle;
root /var/www/shady-nagy.com/html;
index index.html index.htm;
server_name shadynagy.com www.shadynagy.com;
access_log /var/log/nginx/nginx.vhost.access.log;
error_log /var/log/nginx/nginx.vhost.error.log;
location / {
try_files $uri $uri/ =404;
}
}

Key changes explained:

  1. listen 443 ssl http2;

    • 443 = HTTPS port
    • ssl = Enable SSL
    • http2 = Enable HTTP/2 for better performance
    • This replaces the deprecated ssl on; directive
  2. ssl_certificate now points to fullchain instead of just your certificate

  3. ssl_trusted_certificate is kept for OCSP stapling (optional but recommended)

Step 4: Test and Apply Changes

Test the configuration:

nginx -t

Expected output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If you see any errors, double-check your file paths and syntax!

Apply the changes:

systemctl reload nginx

Why reload instead of restart?

  • reload applies changes without dropping connections
  • restart would briefly take your site offline

✅ Benefits of This Fix

  • ✅ All browsers can verify your SSL certificate
  • ✅ No more “Invalid Certificate” warnings
  • ✅ Mobile devices connect properly
  • ✅ Better trust and SEO rankings
  • ✅ Professional appearance

Solution #2: Forcing HTTPS Redirects

The Investigation Journey

First, I checked my HTTP configuration:

cat /etc/nginx/conf.d/shadynagy.com.conf

I found this:

# ❌ BROKEN CONFIGURATION
server {
listen 80;
root /var/www/shady-nagy.com/html;
server_name shadynagy.com www.shadynagy.com;
location / {
try_files $uri $uri/ =404;
}
return 301 https://$server_name$request_uri; # Never reached!
}

The problem: Nginx reads configurations top to bottom. When a request came in:

  1. It matched the location / block
  2. Processed the request
  3. NEVER reached the redirect line

It’s like putting a “Detour” sign AFTER the road!

Step 1: Creating the Correct Redirect Configuration

The fix:

# ✅ CORRECT CONFIGURATION
server {
listen 80;
listen [::]:80; # Also listen on IPv6
server_name shadynagy.com www.shadynagy.com;
# Redirect ALL HTTP traffic to HTTPS
return 301 https://$host$request_uri;
}

What each part does:

  • listen 80; - Listen for HTTP requests (port 80)
  • listen [::]:80; - Also listen for IPv6 HTTP requests
  • server_name - Which domains this applies to
  • return 301 - Send a permanent redirect (301 status code)
  • $host - The domain the user typed (preserves www vs non-www)
  • $request_uri - The path they requested (e.g., /about or /contact)

Example in action:

If someone visits: http://www.shadynagy.com/how-i-fixed-ssl-certificate-issues-on-my-website-a-complete-guide
They’re redirected to: https://www.shadynagy.com/how-i-fixed-ssl-certificate-issues-on-my-website-a-complete-guide

Step 2: Apply and Test

Test the configuration:

nginx -t

Reload nginx:

systemctl reload nginx

Test the redirect:

curl -I http://shadynagy.com

Expected output:

HTTP/1.1 301 Moved Permanently
Location: https://shadynagy.com/

Perfect! But wait…

The Plot Twist: The Firewall Was Blocking Port 80!

When I tested in my browser, it still didn’t work! After some investigation:

firewall-cmd --list-all

Output:

services: https

Notice what’s missing? HTTP (port 80)!

The redirect was configured perfectly, but my firewall was blocking incoming HTTP traffic. It’s like installing a front door but building a wall in front of it!

Step 3: Opening the Firewall

Add HTTP service:

firewall-cmd --permanent --add-service=http

Reload firewall:

firewall-cmd --reload

Verify it worked:

firewall-cmd --list-all

Now you should see:

services: http https

✅ Benefits of This Fix

  • ✅ All visitors automatically use HTTPS
  • ✅ No duplicate content issues for SEO
  • ✅ Better security for your visitors
  • ✅ Improved search engine rankings
  • ✅ Green padlock in browsers
  • ✅ Builds trust with visitors

Complete Diagnostic Commands

Here are all the commands I used during troubleshooting:

Check Nginx Configuration

# Find all server blocks listening on port 80
grep -r "listen 80" /etc/nginx/
# Test nginx configuration syntax
nginx -t
# Show complete nginx configuration
nginx -T
# View specific server block
nginx -T 2>/dev/null | grep -A 15 "server_name shadynagy.com"

Check Nginx Status

# Is nginx running?
systemctl status nginx
# Reload nginx (apply changes without downtime)
systemctl reload nginx
# Restart nginx (brief downtime)
systemctl restart nginx
# Check error logs
tail -20 /var/log/nginx/error.log

Test HTTP to HTTPS Redirect

# Test redirect (bypasses browser cache)
curl -I http://shadynagy.com
# Follow all redirects
curl -IL http://shadynagy.com

Check Port Status

# Is nginx listening on port 80?
ss -tlnp | grep :80
# Is nginx listening on port 443?
ss -tlnp | grep :443

Check Firewall

# List all firewall rules
firewall-cmd --list-all
# Add HTTP permanently
firewall-cmd --permanent --add-service=http
# Add HTTPS permanently
firewall-cmd --permanent --add-service=https
# Apply changes
firewall-cmd --reload

Final Configuration Files

HTTP Configuration (Port 80)

File: /etc/nginx/conf.d/shadynagy.com.conf

server {
listen 80;
listen [::]:80;
server_name shadynagy.com www.shadynagy.com;
return 301 https://$host$request_uri;
}

HTTPS Configuration (Port 443)

File: /etc/nginx/conf.d/shadynagy.com-ssl.conf

server {
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/shadynagy.com-fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/shadynagy.com.key;
ssl_trusted_certificate /etc/nginx/ssl/shadynagy.com.ca-bundle;
root /var/www/shady-nagy.com/html;
index index.html index.htm index.nginx-debian.html;
server_name shadynagy.com www.shadynagy.com;
access_log /var/log/nginx/nginx.vhost.access.log;
error_log /var/log/nginx/nginx.vhost.error.log;
location / {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
try_files $uri $uri/ =404;
}
}

The Results: Everything Fixed! ✅

After implementing all fixes, my SSL test at WhyNoPadlock.com showed:

SSL Connection - Pass
Valid Certificate - SSL Certificate is installed correctly
Force HTTPS - Webserver is forcing the use of SSL
Domain Matching - Certificate matches domain name
Signature - Using sha256WithRSAEncryption
Expiration Date - Certificate is current (expires 2026-11-03)
Mixed Content - No mixed content

Perfect score!

Questions & Answers

Q1: Why do I need an intermediate certificate?

A: The intermediate certificate creates a “chain of trust” from your website to a trusted root authority. Without it:

  • Browsers can’t verify your certificate is legitimate
  • Visitors see scary security warnings
  • Mobile devices often fail to connect
  • Your site looks unprofessional and unsafe

Think of it like this: If someone introduces themselves as “John, friend of Sarah,” you might not trust them. But if Sarah is your best friend and she vouches for John, you trust him. The intermediate certificate is Sarah vouching for your website’s certificate.

Q2: What’s the difference between ssl_certificate and ssl_trusted_certificate in nginx?

A: Great question!

  • ssl_certificate: This is what nginx sends to browsers to prove your website’s identity. It should contain your certificate AND the intermediate certificate (fullchain).

  • ssl_trusted_certificate: This is used for OCSP stapling, a performance feature. Nginx uses this to verify certificate revocation status on behalf of clients. It doesn’t get sent to browsers.

Analogy:

  • ssl_certificate = Your ID card you show to people
  • ssl_trusted_certificate = Your personal copy of the rules you reference for yourself

Q3: Why use $host instead of $server_name in redirects?

A:

  • $host: The exact domain the user typed in their browser (e.g., www.shadynagy.com or shadynagy.com)
  • $server_name: The first server_name in your configuration

Example scenario:

  • Your config has: server_name shadynagy.com www.shadynagy.com;
  • User visits: http://www.shadynagy.com

With $server_name: Redirects to https://shadynagy.com (drops the www)
With $host: Redirects to https://www.shadynagy.com (preserves the www)

Using $host respects what the user typed and prevents unnecessary additional redirects.

Q4: Why did the redirect work with curl but not in my browser?

A: This is usually one of three issues:

  1. Firewall blocking port 80 (my problem!) - The redirect configuration was correct, but incoming HTTP traffic never reached nginx.

  2. Browser cache - Browsers cache redirects aggressively. Solution: Test in incognito mode or clear cache.

  3. DNS cache - Your computer might have old DNS records. Solution: Flush DNS cache or wait a few hours.

Pro tip: Always test with curl -I first because it bypasses all caching!

Q5: Should I use return or rewrite for HTTPS redirects?

A: Always use return for redirects!

return (recommended):

return 301 https://$host$request_uri;
  • Faster (nginx stops processing immediately)
  • Clearer intent
  • Less error-prone

rewrite (avoid for simple redirects):

rewrite ^ https://$host$request_uri permanent;
  • Slower (uses regex engine)
  • More complex
  • Easy to create infinite loops

Rule of thumb: Use return for redirects, use rewrite only when you need to modify the URL structure.

Q6: What’s the difference between reload and restart for nginx?

A:

systemctl reload nginx (recommended):

  • Applies configuration changes gracefully
  • Keeps existing connections alive
  • No downtime
  • Tests config before applying (won’t reload if config is broken)

systemctl restart nginx (use sparingly):

  • Stops nginx completely, then starts it again
  • Drops all active connections
  • Brief downtime (usually milliseconds to seconds)
  • Useful when reload doesn’t work

When to restart instead of reload:

  • When adding new modules
  • When reload fails to pick up changes
  • When debugging weird issues

Q7: How do I know if my firewall is blocking traffic?

A: Run these diagnostic commands:

# Check if nginx is listening on port 80
ss -tlnp | grep :80
# If you see nginx here, it's listening
# Check firewall rules
firewall-cmd --list-all
# Look for 'http' in the services list
# Test from outside your server
curl -I http://your-domain.com
# If it times out, firewall might be blocking

If nginx is listening BUT curl from outside times out → firewall is the problem!

Q8: My SSL test shows “Mixed Content” warnings. What does this mean?

A: Mixed content happens when your HTTPS page loads resources (images, scripts, CSS) over HTTP.

Example problem:

<!-- ❌ BAD - Loading image over HTTP on HTTPS page -->
<img src="http://shadynagy.com/image.jpg">

Solutions:

<!-- ✅ GOOD - Use HTTPS -->
<img src="https://shadynagy.com/image.jpg">
<!-- ✅ GOOD - Use protocol-relative URL -->
<img src="//shadynagy.com/image.jpg">
<!-- ✅ BEST - Use relative URL -->
<img src="/image.jpg">

Check for mixed content:

  1. Open your website in Chrome
  2. Press F12 (Developer Tools)
  3. Look for warnings in the Console tab

Q9: Do I need to do this every time I renew my SSL certificate?

A: Partially, yes.

What you need to do again:

  • Create a new fullchain certificate (combining new cert + intermediate)
  • Replace the old fullchain file

What you DON’T need to do again:

  • Modify nginx configuration files (they’ll reference the same paths)
  • Change firewall rules
  • Set up redirects

Pro tip: Automate certificate renewal with Let’s Encrypt/Certbot, which handles the fullchain automatically!

Q10: Can I test my SSL configuration before making it live?

A: Absolutely! Here’s how:

1. Test nginx configuration syntax:

nginx -t

2. Test locally with curl:

# Test redirect
curl -I http://localhost
# Test HTTPS (might get certificate warning if testing locally)
curl -Ik https://localhost

3. Use online tools:

4. Test in staging environment: If possible, set up a test subdomain (like staging.shadynagy.com) and test there first.

Q11: What’s HTTP/2 and why did you add it?

A: HTTP/2 is a newer, faster version of the HTTP protocol.

Benefits:

  • ✅ Multiplexing: Multiple files download simultaneously over one connection
  • ✅ Header compression: Smaller requests/responses
  • ✅ Server push: Server can send files before browser asks
  • ✅ Better mobile performance

How to enable:

listen 443 ssl http2; # Just add 'http2' here!

Requirements:

  • HTTPS must be enabled (HTTP/2 requires SSL)
  • Nginx 1.9.5 or later
  • OpenSSL 1.0.2 or later

Verification:

curl -I --http2 https://shadynagy.com
# Look for "HTTP/2 200" in the response

Q12: What happens if I forget to make my firewall rules permanent?

A: If you don’t use --permanent, your firewall rules will disappear when you reboot the server!

Non-permanent (lost on reboot):

firewall-cmd --add-service=http # ❌ Gone after reboot

Permanent (survives reboot):

firewall-cmd --permanent --add-service=http # ✅ Stays after reboot
firewall-cmd --reload # Apply immediately

Check if a rule is permanent:

# Show runtime (current) rules
firewall-cmd --list-all
# Show permanent (saved) rules
firewall-cmd --permanent --list-all

If they don’t match, you need to make your changes permanent!

Q13: How often should I check my SSL certificate?

A: Here’s a good maintenance schedule:

Monthly:

  • Check certificate expiration date
  • Run a quick SSL test at WhyNoPadlock.com

Before expiration:

  • Renew certificate 30 days before it expires
  • Test the new certificate immediately
  • Set up automated renewal if possible

After server updates:

  • Test SSL after any nginx/OpenSSL updates
  • Verify redirects still work

Set up monitoring: Many services offer free SSL monitoring:

  • UptimeRobot
  • Pingdom
  • SSL Labs monitoring

They’ll email you before your certificate expires!

Q14: What if I have multiple domains on one server?

A: You need a separate server block for each domain.

Example for multiple domains:

# Domain 1: shadynagy.com
server {
listen 80;
server_name shadynagy.com www.shadynagy.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name shadynagy.com www.shadynagy.com;
ssl_certificate /etc/nginx/ssl/shadynagy.com-fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/shadynagy.com.key;
root /var/www/shadynagy.com;
}
# Domain 2: myotherdomain.com
server {
listen 80;
server_name myotherdomain.com www.myotherdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name myotherdomain.com www.myotherdomain.com;
ssl_certificate /etc/nginx/ssl/myotherdomain.com-fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/myotherdomain.com.key;
root /var/www/myotherdomain.com;
}

Alternative: Use a wildcard certificate or multi-domain (SAN) certificate to cover multiple domains with one certificate.

Q15: Why is my certificate showing as “not secure” even after all these fixes?

A: Check these common issues:

  1. Certificate name mismatch:

    • Certificate is for www.shadynagy.com but you’re visiting shadynagy.com
    • Solution: Get a certificate that covers both (SAN certificate)
  2. Expired certificate:

    • Check expiration date: openssl x509 -in certificate.crt -noout -dates
    • Solution: Renew certificate
  3. Mixed content:

    • HTTPS page loading HTTP resources
    • Check browser console for warnings
  4. Wrong domain in nginx config:

    • server_name doesn’t match the domain you’re visiting
    • Solution: Add all domain variations to server_name
  5. Certificate not trusted:

    • Using self-signed certificate
    • Solution: Get certificate from trusted CA (Let’s Encrypt is free!)

Quick diagnosis:

# Check what certificate is being served
openssl s_client -connect shadynagy.com:443 -servername shadynagy.com

Useful Online SSL Testing Tools

🔗 SSL Labs - Most comprehensive SSL test, gives you an A-F grade

🔗 WhyNoPadlock - Quick visual check for common SSL issues

🔗 SSL Shopper - Checks certificate installation and chain

🔗 Security Headers - Tests HTTP security headers

🔗 High-Tech Bridge - Detailed SSL/TLS and security assessment

Key Takeaways

For Intermediate Certificates:

✅ Always create a fullchain certificate (cert + CA bundle)
✅ Use ssl_certificate directive for the fullchain
✅ Use ssl_trusted_certificate only for OCSP stapling
✅ Test with online tools after installation

For HTTPS Redirects:

✅ Use return 301 for redirects (not rewrite)
✅ Place redirect BEFORE location blocks
✅ Use $host to preserve user’s domain input
✅ Remember to open port 80 in the firewall!

For Testing:

✅ Always test nginx config with nginx -t before reloading
✅ Use curl to bypass browser cache
✅ Check firewall rules with firewall-cmd --list-all
✅ Verify port listening with ss -tlnp

For Maintenance:

✅ Set up certificate expiration alerts
✅ Make firewall rules permanent
✅ Keep nginx and OpenSSL updated
✅ Regularly test SSL configuration

Final Thoughts

SSL configuration might seem daunting at first, but once you understand the pieces, it’s quite logical:

  1. Certificate chain - Prove your identity
  2. HTTPS redirect - Force secure connections
  3. Firewall rules - Allow traffic to reach your server

Breaking down the problem into these components made it much easier to diagnose and fix.

The most important lesson? When something doesn’t work, check layer by layer:

  • Is nginx configured correctly? (nginx -t)
  • Is nginx listening? (ss -tlnp)
  • Is the firewall allowing traffic? (firewall-cmd --list-all)
  • Does it work locally? (curl -I http://localhost)

This systematic approach saved me hours of frustration!

Feedback and Questions

We’d love to hear your feedback on this tutorial! If you have any questions or suggestions for improvement, please don’t hesitate to reach out. You can leave a comment below, or you can contact us through the following channels:

  1. Email: info@shadynagy.com
  2. Twitter: @ShadyNagy_
  3. LinkedIn: Shady Nagy
  4. GitHub: ShadyNagy

Found this helpful? Share it with someone struggling with SSL configuration!


Tags

#SSL#HTTPS#SSLCertificate#Nginx#RockyLinux#Linux#WebSecurity#SSLTroubleshooting#ServerConfiguration#IntermediateCertificate#Firewall#Firewalld#CertificateChain#HTTPSRedirect#NginxSSL#ForceHTTPS#Sectigo#LetsEncrypt#WebServerSetup#SSLConfiguration#TLS#RockyLinux9

Share


Previous Article
Why Your EF Core Tests Are Lying to You (And How SQLite In-Memory Fixes It)
Shady Nagy

Shady Nagy

Software Innovation Architect

Topics

AI
Angular
dotnet
GatsbyJS
Github
Linux
MS SQL
Oracle

Related Posts

Allowing Nginx to Connect to the Network on Rocky Linux 9
Allowing Nginx to Connect to the Network on Rocky Linux 9
June 07, 2024
2 min

Quick Links

Contact Us

Social Media