- 12 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.


Certificate Warning After Machine Rename or Clone

Problem: Browser shows NET::ERR_CERT_COMMON_NAME_INVALID even though the certificate appears correct and is properly bound in IIS. The server was either renamed after the initial installation or was provisioned from a VM image (clone, template, snapshot restore) that already had a hostname baked in.

Root Cause: The certificate was issued for the original machine name. After a rename or clone, the hostname users browse to no longer matches any SAN entry in the certificate. The certificate is cryptographically valid but covers the wrong name.

A secondary symptom is finding two different hostnames in the certificate SAN — one being the old/image name, one being the current machine name — because the certificate was re-issued to patch the mismatch without removing the legacy entry.

How to Confirm

Compare what is in the certificate SAN against what users actually browse to:

# Show all SAN entries on the bound certificate
$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -like "A1B2*" }  # replace with your thumbprint prefix
$cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } | ForEach-Object { $_.Format($true) }

If the hostname users browse to is not listed, the certificate must be re-issued to include it.

Solution: Re-Issue the Certificate with Both Names in the SAN

Request a new certificate from your internal CA that includes all current DNS names the server is accessed by:

SAN entry When to include
nodinite.yourdomain.com (DNS alias / CNAME) ✅ Required if users browse via an alias — this is the recommended production pattern
nodinite-appsrv-001.yourdomain.com (machine FQDN) ✅ Recommended — needed for direct server access, monitoring agents, and admin tools
appsrv-old.yourdomain.com (old name / image name) ⚠️ Optional — include only while systems still reference the old name; remove when safe
Short machine name (e.g. appsrv-old) Only if users or agents connect using the NetBIOS name without domain suffix

Multiple SAN entries on a single certificate are completely normal and have no negative effects. A browser only requires that the hostname it is connecting to appears somewhere in the SAN list.

DNS alias (CNAME) is the recommended production pattern. Create a DNS CNAME record nodinite.yourdomain.com → nodinite-appsrv-001.yourdomain.com and register Nodinite in the Portal using the alias as the hostname. Users bookmark the friendly alias; if the server is ever renamed, rebuilt, or migrated, you update only the DNS record — not the certificate, not the Portal config, not user bookmarks. The certificate SAN must include the alias name, not just the machine FQDN.

For an internal AD CS environment, re-request using your organisation's web server certificate template and include all required DNS names in the Subject Alternative Name field of the certificate request.

After re-issuing:

  1. Import the new certificate into Cert:\LocalMachine\My on the IIS server
  2. Update the IIS HTTPS binding to select the new certificate (IIS Manager → Site → Bindings → Edit → select new cert)
  3. Update the Certificate Thumbprint in the Nodinite Portal → Environment → TLS tab
  4. Re-run the installation script so all service bindings use the new thumbprint

Also check for LDAP-only CRL/OCSP — machines provisioned from internal PKI images often have certificates with only ldap:// revocation URLs, which browsers cannot follow. Run the CRL/OCSP diagnostic script on the new certificate before considering the issue resolved. Re-issuing from the CA is also the right time to request an HTTP CDP be added to the template.


Next Steps