
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.
Before diving into solutions, let’s understand what these errors actually mean:
What’s an intermediate certificate?
Think of SSL certificates like a chain of trust:
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:
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:
I discovered my nginx configuration was using the wrong directive. Here’s what I had:
# ❌ WRONG CONFIGURATIONssl_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!
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> - Outputs to a new file called “fullchain”Example explanation: Imagine you have two puzzle pieces:
You’re gluing them together to create one complete puzzle that browsers can understand.
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 certificatessl_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:
listen 443 ssl http2;
443 = HTTPS portssl = Enable SSLhttp2 = Enable HTTP/2 for better performancessl on; directivessl_certificate now points to fullchain instead of just your certificate
ssl_trusted_certificate is kept for OCSP stapling (optional but recommended)
Test the configuration:
nginx -t
Expected output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: 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 connectionsrestart would briefly take your site offlineFirst, I checked my HTTP configuration:
cat /etc/nginx/conf.d/shadynagy.com.conf
I found this:
# ❌ BROKEN CONFIGURATIONserver {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:
location / blockIt’s like putting a “Detour” sign AFTER the road!
The fix:
# ✅ CORRECT CONFIGURATIONserver {listen 80;listen [::]:80; # Also listen on IPv6server_name shadynagy.com www.shadynagy.com;# Redirect ALL HTTP traffic to HTTPSreturn 301 https://$host$request_uri;}
What each part does:
listen 80; - Listen for HTTP requests (port 80)listen [::]:80; - Also listen for IPv6 HTTP requestsserver_name - Which domains this applies toreturn 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
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 PermanentlyLocation: https://shadynagy.com/
Perfect! But wait…
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!
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
Here are all the commands I used during troubleshooting:
# Find all server blocks listening on port 80grep -r "listen 80" /etc/nginx/# Test nginx configuration syntaxnginx -t# Show complete nginx configurationnginx -T# View specific server blocknginx -T 2>/dev/null | grep -A 15 "server_name shadynagy.com"
# Is nginx running?systemctl status nginx# Reload nginx (apply changes without downtime)systemctl reload nginx# Restart nginx (brief downtime)systemctl restart nginx# Check error logstail -20 /var/log/nginx/error.log
# Test redirect (bypasses browser cache)curl -I http://shadynagy.com# Follow all redirectscurl -IL http://shadynagy.com
# Is nginx listening on port 80?ss -tlnp | grep :80# Is nginx listening on port 443?ss -tlnp | grep :443
# List all firewall rulesfirewall-cmd --list-all# Add HTTP permanentlyfirewall-cmd --permanent --add-service=http# Add HTTPS permanentlyfirewall-cmd --permanent --add-service=https# Apply changesfirewall-cmd --reload
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;}
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;}}
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!
A: The intermediate certificate creates a “chain of trust” from your website to a trusted root authority. Without it:
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.
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 peoplessl_trusted_certificate = Your personal copy of the rules you reference for yourself$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 configurationExample scenario:
server_name shadynagy.com www.shadynagy.com;http://www.shadynagy.comWith $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.
A: This is usually one of three issues:
Firewall blocking port 80 (my problem!) - The redirect configuration was correct, but incoming HTTP traffic never reached nginx.
Browser cache - Browsers cache redirects aggressively. Solution: Test in incognito mode or clear cache.
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!
return or rewrite for HTTPS redirects?A: Always use return for redirects!
return (recommended):
return 301 https://$host$request_uri;
rewrite (avoid for simple redirects):
rewrite ^ https://$host$request_uri permanent;
Rule of thumb: Use return for redirects, use rewrite only when you need to modify the URL structure.
reload and restart for nginx?A:
systemctl reload nginx (recommended):
systemctl restart nginx (use sparingly):
When to restart instead of reload:
A: Run these diagnostic commands:
# Check if nginx is listening on port 80ss -tlnp | grep :80# If you see nginx here, it's listening# Check firewall rulesfirewall-cmd --list-all# Look for 'http' in the services list# Test from outside your servercurl -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!
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:
A: Partially, yes.
What you need to do again:
What you DON’T need to do again:
Pro tip: Automate certificate renewal with Let’s Encrypt/Certbot, which handles the fullchain automatically!
A: Absolutely! Here’s how:
1. Test nginx configuration syntax:
nginx -t
2. Test locally with curl:
# Test redirectcurl -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.
A: HTTP/2 is a newer, faster version of the HTTP protocol.
Benefits:
How to enable:
listen 443 ssl http2; # Just add 'http2' here!
Requirements:
Verification:
curl -I --http2 https://shadynagy.com# Look for "HTTP/2 200" in the response
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 rebootfirewall-cmd --reload # Apply immediately
Check if a rule is permanent:
# Show runtime (current) rulesfirewall-cmd --list-all# Show permanent (saved) rulesfirewall-cmd --permanent --list-all
If they don’t match, you need to make your changes permanent!
A: Here’s a good maintenance schedule:
Monthly:
Before expiration:
After server updates:
Set up monitoring: Many services offer free SSL monitoring:
They’ll email you before your certificate expires!
A: You need a separate server block for each domain.
Example for multiple domains:
# Domain 1: shadynagy.comserver {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.comserver {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.
A: Check these common issues:
Certificate name mismatch:
www.shadynagy.com but you’re visiting shadynagy.comExpired certificate:
openssl x509 -in certificate.crt -noout -datesMixed content:
Wrong domain in nginx config:
server_name doesn’t match the domain you’re visitingserver_nameCertificate not trusted:
Quick diagnosis:
# Check what certificate is being servedopenssl s_client -connect shadynagy.com:443 -servername shadynagy.com
🔗 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
✅ 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
✅ 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!
✅ 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
✅ Set up certificate expiration alerts
✅ Make firewall rules permanent
✅ Keep nginx and OpenSSL updated
✅ Regularly test SSL configuration
SSL configuration might seem daunting at first, but once you understand the pieces, it’s quite logical:
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:
nginx -t)ss -tlnp)firewall-cmd --list-all)curl -I http://localhost)This systematic approach saved me hours of frustration!
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:
Found this helpful? Share it with someone struggling with SSL configuration!
Quick Links
Legal Stuff





