Beginner 10 min HTTP 301

Mixed Content Errors After HTTPS Migration

Симптомы

- Browser console shows: `Mixed Content: The page at 'https://...' was loaded over HTTPS, but requested an insecure resource 'http://...'`
- Images, fonts, or stylesheets fail to load after switching from HTTP to HTTPS
- Chrome padlock shows a warning triangle instead of the green lock icon
- JavaScript `fetch()` or XMLHttpRequest calls to `http://` URLs are blocked
- Analytics or third-party widgets stop working because their embeds still use HTTP

Первопричины

  • Hardcoded `http://` URLs in HTML templates, CSS `url()` references, or database-stored content
  • Third-party scripts or ad networks serving assets over HTTP
  • Django's `MEDIA_URL` or `STATIC_URL` still set to `http://` instead of `//` or `https://`
  • User-generated content (blog posts, CMS fields) containing absolute HTTP links
  • Canonical tags or Open Graph meta tags still pointing to HTTP URLs after migration

Диагностика

1. **Open browser DevTools → Console** and look for Mixed Content warnings:
```
Mixed Content: The page at 'https://example.com/' was loaded over HTTPS, but
requested an insecure script 'http://cdn.example.com/jquery.js'.
```

2. **Scan your codebase for hardcoded `http://` URLs:**
```bash
grep -r 'http://' templates/ static/ \
--include='*.html' --include='*.css' --include='*.js' | \
grep -v 'localhost\|127.0.0.1\|http://schemas'
```

3. **Check Django settings for HTTP asset URLs:**
```bash
grep -r 'MEDIA_URL\|STATIC_URL\|SITE_URL\|BASE_URL' config/settings/
# Should use https:// or protocol-relative //
```

4. **Scan database for HTTP links** in content fields (PostgreSQL):
```sql
SELECT id, content FROM blog_post WHERE content LIKE '%http://%'
LIMIT 20;
```

5. **Use the Network tab** (filter by `http://`) to find all mixed content resources:
In Chrome DevTools → Network → filter URL column with `http://`

Решение

**Fix 1: Add `upgrade-insecure-requests` CSP header** to auto-upgrade HTTP → HTTPS for passive content (images, media) — quick win:
```nginx
# /etc/nginx/sites-available/myapp
add_header Content-Security-Policy 'upgrade-insecure-requests';
```
Or in Django middleware:
```python
# config/settings/production.py
SECURE_CONTENT_TYPE_NOSNIFF = True
# Add CSP header via django-csp or custom middleware:
# Content-Security-Policy: upgrade-insecure-requests
```

**Fix 2: Update Django settings** to use HTTPS or protocol-relative URLs:
```python
# config/settings/production.py
STATIC_URL = 'https://cdn.example.com/static/' # or '/static/'
MEDIA_URL = 'https://cdn.example.com/media/' # or '/media/'
```

**Fix 3: Bulk-update database content** to replace HTTP → HTTPS:
```sql
-- PostgreSQL: replace http:// with https:// in all content fields
UPDATE blog_post
SET content = REPLACE(content, 'http://example.com', 'https://example.com')
WHERE content LIKE '%http://example.com%';
```

**Fix 4: Add HSTS header** once all mixed content is resolved to enforce HTTPS in future:
```nginx
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains' always;
```

Профилактика

- **Use protocol-relative URLs** (`//cdn.example.com/`) or always `https://` in templates and CSS — never hardcode `http://`
- **Test in a staging environment first** with HTTPS before migrating production — run the full mixed content audit in DevTools before cutover
- **Enable `upgrade-insecure-requests` CSP** as a safety net on day one of migration, then audit and fix remaining hardcoded URLs before removing it
- **Set up a pre-commit hook or CI check** that fails on `http://` URLs in templates, preventing regressions from future commits

Связанные коды состояния

Связанные термины