- 9 minutes to read

TLS/HTTPS Troubleshooting for Nodinite v7

Common issues encountered after enabling TLS/HTTPS on Nodinite v7 installations. For the main hardening guide, see How to perform hardening.

Quick Navigation


Slow Page Loads: Certificate Revocation Timeout (CRL/OCSP)

Problem: Browser and Swagger UI take 20-30 seconds to load every page, but curl and API calls from PowerShell are fast. The delay is consistent on every request, not just the first.

Root Cause: Your certificate includes CRL (Certificate Revocation List) or OCSP (Online Certificate Status Protocol) URLs that browsers try to contact before rendering each page. If the IIS server or client workstations cannot reach these URLs (blocked firewall, unreachable CA), browsers wait for the full timeout before proceeding. curl is unaffected because it does not check certificate revocation by default.

This issue is particularly common with:

  • Let's Encrypt certificates (CRL/OCSP URLs point to r12.c.lencr.org, r13.i.lencr.org)
  • Internal CA certificates where the CRL distribution point is not accessible from the internet or client workstations
  • Air-gapped or restricted-network environments

Diagnose: Check if Your Certificate Has CRL/OCSP URLs

Run this PowerShell script on the IIS server to inspect the certificate and test reachability of revocation endpoints:

#Requires -Version 7.0
# Diagnose CRL/OCSP certificate revocation timeout
# Replace the Subject filter with your actual certificate subject/hostname

$cert = Get-ChildItem -Path Cert:\LocalMachine\My |
    Where-Object { $_.Subject -match "nodinite\.yourdomain\.com" } |  # Replace with your hostname
    Sort-Object NotBefore -Descending |
    Select-Object -First 1

if (-not $cert) {
    Write-Host "❌ Certificate not found - check subject name" -ForegroundColor Red
    Write-Host "Available certificates:" -ForegroundColor Yellow
    Get-ChildItem Cert:\LocalMachine\My | Select-Object Subject, Thumbprint, NotAfter
    exit 1
}

Write-Host "=== Certificate Information ===" -ForegroundColor Cyan
Write-Host "Subject    : $($cert.Subject)" -ForegroundColor White
Write-Host "Issuer     : $($cert.Issuer)" -ForegroundColor White
Write-Host "Thumbprint : $($cert.Thumbprint)" -ForegroundColor White
Write-Host "Valid To   : $($cert.NotAfter)" -ForegroundColor White
if ($cert.Issuer -eq $cert.Subject) {
    Write-Host "Type       : Self-signed" -ForegroundColor Green
} else {
    Write-Host "Type       : CA-issued ($($cert.Issuer))" -ForegroundColor Yellow
}

# Check for CRL Distribution Points
Write-Host "`n=== CRL Distribution Points ===" -ForegroundColor Cyan
$crlExt = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "CRL Distribution Points" }
if ($crlExt) {
    Write-Host "❌ CRL URLs found (may cause 20-30s browser timeout if unreachable):" -ForegroundColor Red
    $crlText = $crlExt.Format($false)
    $crlText
    # Extract and test each URL
    [regex]::Matches($crlText, 'http[^\s]+') | ForEach-Object {
        $url = $_.Value.TrimEnd(')')
        Write-Host "`nTesting: $url" -ForegroundColor Yellow
        $t = Measure-Command {
            try { Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop | Out-Null; Write-Host "  ✓ Reachable" -ForegroundColor Green }
            catch { Write-Host "  ❌ UNREACHABLE - THIS CAUSES THE 20-30s TIMEOUT!" -ForegroundColor Red }
        }
        Write-Host "  Response time: $([math]::Round($t.TotalSeconds,1))s" -ForegroundColor $(if ($t.TotalSeconds -lt 3) { "Green" } else { "Red" })
    }
} else {
    Write-Host "✓ No CRL Distribution Points (no revocation URL to time out on)" -ForegroundColor Green
}

# Check for OCSP (Authority Information Access)
Write-Host "`n=== OCSP / Authority Information Access ===" -ForegroundColor Cyan
$aiaExt = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Authority Information Access" }
if ($aiaExt) {
    Write-Host "❌ OCSP/AIA URLs found (may cause browser timeout if unreachable):" -ForegroundColor Red
    $aiaText = $aiaExt.Format($false)
    $aiaText
    [regex]::Matches($aiaText, 'http[^\s]+') | ForEach-Object {
        $url = $_.Value.TrimEnd(')')
        Write-Host "`nTesting: $url" -ForegroundColor Yellow
        $t = Measure-Command {
            try { Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop | Out-Null; Write-Host "  ✓ Reachable" -ForegroundColor Green }
            catch { Write-Host "  ❌ UNREACHABLE - THIS CAUSES THE 20-30s TIMEOUT!" -ForegroundColor Red }
        }
        Write-Host "  Response time: $([math]::Round($t.TotalSeconds,1))s" -ForegroundColor $(if ($t.TotalSeconds -lt 3) { "Green" } else { "Red" })
    }
} else {
    Write-Host "✓ No OCSP endpoints (no revocation URL to time out on)" -ForegroundColor Green
}

# Summary
Write-Host "`n=== Diagnosis ===" -ForegroundColor Cyan
if ($crlExt -or $aiaExt) {
    Write-Host "Certificate has revocation URLs. If any show UNREACHABLE above:" -ForegroundColor Yellow
    Write-Host "  • curl is fast  → curl does not check CRL/OCSP by default" -ForegroundColor White
    Write-Host "  • Browsers slow → browsers validate revocation on every page load" -ForegroundColor White
    Write-Host "  • Swagger slow  → Swagger uses the browser engine (same behaviour)" -ForegroundColor White
} else {
    Write-Host "✓ Certificate is clean - no revocation URLs. Slow loads have a different cause." -ForegroundColor Green
}

Solutions

OCSP stapling makes the IIS server fetch and cache the revocation response, serving it directly to browsers so they never need to contact the CA's OCSP server themselves. This requires the IIS server to be able to reach the CRL/OCSP endpoints (ensure outbound HTTP/port 80 is allowed from IIS to the CA servers).

# Enable OCSP stapling (IIS 10 / Windows Server 2016+)
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL" `
    -Name "EnableOcspStaplingForSni" -Value 1 -PropertyType DWord -Force

# Apply by restarting IIS (faster than restarting HTTP.sys)
iisreset /noforce

# Verify stapling is active using curl
curl.exe -v https://nodinite.yourdomain.com 2>&1 | Select-String "OCSP"
# Look for: OCSP Response Status: successful

Option 2: Use a Self-Signed Certificate Without Revocation URLs

New-SelfSignedCertificate in PowerShell does not embed CRL/OCSP URLs by default, making it ideal for development and isolated environments:

# Generate clean self-signed certificate (no CRL/OCSP URLs)
$cert = New-SelfSignedCertificate `
    -DnsName "nodinite.yourdomain.com" `       # Replace with your hostname(s)
    -CertStoreLocation "Cert:\LocalMachine\My" `
    -FriendlyName "Nodinite HTTPS" `
    -NotAfter (Get-Date).AddYears(2) `
    -KeyAlgorithm RSA -KeyLength 2048 `
    -HashAlgorithm SHA256 `
    -Provider "Microsoft RSA SChannel Cryptographic Provider"

Write-Host "Thumbprint: $($cert.Thumbprint)" -ForegroundColor Green

# Add to Trusted Root so browsers trust it
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new("Root","LocalMachine")
$store.Open("ReadWrite"); $store.Add($cert); $store.Close()
Write-Host "Certificate trusted in Local Machine Root store" -ForegroundColor Green
# Update the IIS binding and Nodinite Portal TLS thumbprint to use this certificate

Tip

After generating the certificate, update the certificate thumbprint in the Nodinite Portal under Environment → TLS and re-run the installation script so IIS uses the new certificate.

Option 3: Open Outbound Firewall Access to CA Revocation Servers

If you must use a CA-issued certificate (e.g., Let's Encrypt or internal PKI), ensure outbound HTTP (port 80) is allowed from the IIS server to the CRL/OCSP hostnames listed in the diagnostic script output above.

# Example: allow outbound HTTP for Let's Encrypt CRL/OCSP
New-NetFirewallRule -DisplayName "Allow Let's Encrypt CRL/OCSP" `
    -Direction Outbound -Protocol TCP -RemotePort 80,443 `
    -Action Allow -Enabled True -Profile Any

Then verify connectivity and re-run the diagnostic script to confirm all revocation URLs are reachable.

Note

Why does curl not show this problem? curl does not check certificate revocation by default. Use --ssl-revoke-best-effort with curl to simulate browser behaviour. Downloads in browsers are also typically unaffected — revocation checks only apply to full HTTPS page navigations (not file downloads or API calls from non-browser clients).


401 Unauthorized When Accessing via Hostname Locally

Problem: PowerShell installation script or local browser access via FQDN (e.g., https://nodinitedev28.test.acme.com) returns 401 Unauthorized when accessing from the IIS server itself, but works from remote machines.

Root Cause: Windows loopback security check blocks local access to IIS via hostname when using Windows Authentication. By default, Windows treats local requests using a hostname (not localhost or machine name) as potential security risks.

When This Issue Occurs

The loopback security check is enabled by default on ALL Windows installations. Most Nodinite deployments never encounter this because:

  • Remote access pattern — Users access Nodinite from workstations, not from the IIS server itself
  • localhost/IP bypass — The check doesn't apply to https://localhost or https://127.0.0.1
  • NetBIOS tolerance — Accessing via server name (e.g., https://SERVERNAME) may work
  • Remote installation — Setup scripts typically run from an admin workstation, not on the IIS server

All of these conditions must be present simultaneously for the issue to appear:

  1. ✅ Script or browser running locally on the IIS server (not remote)
  2. ✅ Accessing via FQDN (e.g., nodinitedev28.test.acme.com) not localhost/IP/machine name
  3. Windows integrated authentication enabled
  4. ✅ Typically during installation when PowerShell verifies HTTPS bindings immediately after configuration

Solution 1: Disable Loopback Check (Quick Fix — Less Secure)

Run this PowerShell command as Administrator on the IIS server:

New-ItemProperty HKLM:\System\CurrentControlSet\Control\Lsa -Name "DisableLoopbackCheck" -Value "1" -PropertyType dword

Restart required: No — takes effect immediately for new connections.

Warning

Security Impact: Disabling loopback check applies to all hostnames on the server, reducing security. Use Solution 2 for production environments.

Note

Already have BackConnectionHostNames configured? Many enterprise environments already use this registry key for other applications (SharePoint, SQL Server Reporting Services, custom web apps). The script below safely adds your Nodinite hostname to existing entries without creating duplicates.

Whitelist only the specific hostname(s) you need:

# Safe script - prevents duplicates
$regPath = "HKLM:\System\CurrentControlSet\Control\Lsa\MSV1_0"
$newHostname = "nodinitedev28.test.acme.com"  # Replace with your FQDN

# Create registry path if it doesn't exist
if (-not (Test-Path $regPath)) {
    New-Item -Path $regPath -Force | Out-Null
}

# Get existing values (or empty array if not exists)
$existingValues = @()
try {
    $existingValues = (Get-ItemProperty -Path $regPath -Name "BackConnectionHostNames" -ErrorAction Stop).BackConnectionHostNames
    Write-Host "Found existing BackConnectionHostNames: $($existingValues -join ', ')" -ForegroundColor Yellow
} catch {
    Write-Host "BackConnectionHostNames doesn't exist yet - will create it" -ForegroundColor Yellow
}

# Add new hostname only if not already present
if ($existingValues -notcontains $newHostname) {
    $updatedValues = $existingValues + $newHostname
    Set-ItemProperty -Path $regPath -Name "BackConnectionHostNames" -Value $updatedValues -Force
    Write-Host "✓ Added $newHostname to BackConnectionHostNames" -ForegroundColor Green
} else {
    Write-Host "✓ $newHostname already exists in BackConnectionHostNames - no changes needed" -ForegroundColor Cyan
}

# Display final result
Write-Host "`nCurrent BackConnectionHostNames:" -ForegroundColor Yellow
(Get-ItemProperty -Path $regPath -Name "BackConnectionHostNames").BackConnectionHostNames | ForEach-Object { Write-Host "  - $_" -ForegroundColor White }

Restart required: No — takes effect immediately for new connections.

If you previously ran a script that created duplicate entries, clean them up first:

# Remove duplicates from BackConnectionHostNames
$regPath = "HKLM:\System\CurrentControlSet\Control\Lsa\MSV1_0"

# Get current values
$currentValues = (Get-ItemProperty -Path $regPath -Name "BackConnectionHostNames").BackConnectionHostNames

# Remove duplicates while preserving order
$uniqueValues = $currentValues | Select-Object -Unique

# Update registry with unique values only
Set-ItemProperty -Path $regPath -Name "BackConnectionHostNames" -Value $uniqueValues

Write-Host "✓ Cleaned up duplicates" -ForegroundColor Green
Write-Host "`nCurrent BackConnectionHostNames:" -ForegroundColor Yellow
(Get-ItemProperty -Path $regPath -Name "BackConnectionHostNames").BackConnectionHostNames | ForEach-Object { Write-Host "  - $_" -ForegroundColor White }

Verification

  1. Close all browser windows
  2. Open new browser and navigate to https://nodinitedev28.test.acme.com (your FQDN)
  3. You should now successfully authenticate and access Nodinite Web Client

Tip

For production environments, use Solution 2 (BackConnectionHostNames) to limit the security exception to only your Nodinite hostname.


Next Steps