Dokumentacja

Włącz HSTS

Włącz HSTS

Przekierowanie z http:// na https:// chroni odwiedzających dopiero po pierwszym żądaniu, a to pierwsze żądanie idzie bez szyfrowania. W publicznej sieci Wi-Fi ktoś może je przechwycić i zatrzymać odwiedzającego na fałszywej wersji Twojej strony pod http://. HSTS (HTTP Strict Transport Security) zamyka tę lukę: jeden nagłówek odpowiedzi każe przeglądarce przez określony czas używać dla Twojej domeny HTTPS i w ogóle nie próbować już http://.

Nagłówek nic nie kosztuje i respektuje go każda nowoczesna przeglądarka. Lista kontrolna strony oczekuje go z max-age co najmniej 180 dni.

Jak wygląda nagłówek

Strict-Transport-Security: max-age=31536000; includeSubDomains
Część Znaczenie
max-age przez ile sekund przeglądarka pamięta, że ma używać HTTPS. Lista kontrolna wymaga co najmniej 15552000 (180 dni); rok to 31536000.
includeSubDomains reguła obejmuje też wszystkie subdomeny: shop.example.com, mail.example.com, intranet.example.com …
preload prośba o wbudowanie w przeglądarki (zobacz ostrzeżenie poniżej). Opcjonalne.

Przeglądarki przyjmują nagłówek tylko przez HTTPS; w odpowiedziach na http:// go ignorują. Najpierw upewnij się, że przekierowanie z HTTP na HTTPS działa.

Ostrzeżenie: HSTS to obietnica, której przeglądarka będzie Cię trzymać. Dopóki trwa max-age, przeglądarka odwiedzającego nie otworzy strony przez zwykły HTTP ani z błędem certyfikatu, a przycisku "kontynuuj mimo to" nie ma. Zanim dodasz includeSubDomains, upewnij się, że każda subdomena ma ważny certyfikat, także stare, które działają tylko na http://.

Zacznij nisko, potem podnieś

  1. Zacznij od max-age=300 (pięć minut) i sprawdź stronę oraz jej subdomeny.
  2. Na kilka dni podnieś do 86400 (jeden dzień).
  3. Potem ustaw 31536000 (rok).

Jeśli przy krótkiej wartości coś się zepsuje, możesz usunąć nagłówek, a przeglądarki zapomną go w kilka minut. Przy roku pamiętają go przez rok.

O preload

Słowo preload i lista na hstspreload.org wbudowują Twoją domenę w Chrome, Firefox, Safari i Edge, więc nawet pierwsza wizyta używa HTTPS. Lista wymaga max-age co najmniej 31536000, includeSubDomains i preload. Usunięcie z listy trwa miesiącami i dociera tylko do odwiedzających z zaktualizowanymi przeglądarkami. Dodaj preload tylko wtedy, gdy masz pewność, że cała domena ze wszystkimi subdomenami zostanie na HTTPS na zawsze.

Sprawdź sam

Zamiast example.com wpisz swoją stronę:

curl -sI https://example.com | grep -i '^strict-transport-security'
curl -sI https://example.com/no-such-page | grep -i '^strict-transport-security'

Po naprawie: obie linie wypisują nagłówek z max-age=15552000 lub więcej. Drugie polecenie sprawdza, czy mają go też strony błędów; w przykładach dla nginx i Apache poniżej odpowiada za to always.

Jak to naprawić

Ustaw nagłówek w jednym miejscu, jak najbliżej odwiedzającego: CDN lub serwer WWW obejmuje każdą odpowiedź, także obrazy i strony z pamięci podręcznej. Wybierz to, do czego masz dostęp.

WordPress

Jeśli możesz, ustaw nagłówek w panelu hostingu lub w .htaccess (zobacz te zakładki): wtedy obejmuje też strony z pamięci podręcznej i pliki, które nigdy nie docierają do PHP.

Jeśli masz tylko dostęp do panelu WordPressa, dodaj to darmową wtyczką Code Snippets (Snippets → Add New, uruchamiaj wszędzie) albo w functions.php motywu potomnego:

// HSTS na każdej stronie, którą WordPress generuje przez HTTPS.
add_action('send_headers', function () {
    if (is_ssl()) {
        header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
    }
});

Potem wyczyść pamięć podręczną stron. Strony serwowane prosto z cache (WP Rocket, LiteSpeed Cache) mogą nadal nie mieć nagłówka; jeśli sprawdzenie curl pokaże jego brak, użyj zakładki dla serwera.

cPanel

Hosting współdzielony z cPanelem zwykle nie ma przełącznika HSTS, ale .htaccess działa. Dodaj to do .htaccess w katalogu głównym strony (File Manager, z włączonym Show Hidden Files):

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>

Przeglądarki ignorują nagłówek w odpowiedziach na http://, więc tam nie szkodzi; liczy się, żeby był w każdej odpowiedzi na https://.

Plesk

  1. Otwórz Websites & Domains → example.com → SSL/TLS Certificates.
  2. Włącz HSTS i ustaw max-age na co najmniej 6 miesięcy (lepiej rok). Include subdomains zaznacz dopiero po ich sprawdzeniu, a Preload zostaw wyłączone, chyba że naprawdę tego chcesz.

Jeśli Twoja wersja Pleska nie ma tam przełącznika HSTS, dodaj nagłówek w Websites & Domains → example.com → Apache & nginx Settings, w Additional nginx directives:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

nginx

W bloku server dla HTTPS Twojej strony (/etc/nginx/sites-available/example.com na Debianie i Ubuntu, /etc/nginx/conf.d/example.com.conf na Rocky, Alma i CentOS):

server {
    listen 443 ssl;
    server_name example.com;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

Bez always nginx nie dodaje nagłówka do odpowiedzi z błędem. Pułapka: blok location, który ma własne add_header, nie dziedziczy żadnego nagłówka z bloku server, więc powtórz tam tę linię.

sudo nginx -t && sudo systemctl reload nginx

Apache i LiteSpeed

Z dostępem do serwera (Apache). W <VirtualHost *:443> Twojej strony (/etc/apache2/sites-available/example.com-le-ssl.conf lub podobny na Debianie i Ubuntu, /etc/httpd/conf.d/ na Rocky, Alma i CentOS):

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

Wymaga mod_headers (na Debianie i Ubuntu: sudo a2enmod headers).

sudo apachectl configtest && sudo systemctl reload apache2

(Na Rocky, Alma i CentOS httpd zamiast apache2.)

Bez dostępu do serwera (hosting współdzielony, LiteSpeed). Użyj linii .htaccess z zakładki cPanel; LiteSpeed czyta je tak samo.

Cloudflare

  1. Otwórz domenę i przejdź do SSL/TLS → Edge Certificates.
  2. W HTTP Strict Transport Security (HSTS) kliknij Enable HSTS i potwierdź ostrzeżenie.
  3. Ustaw Max Age Header na 6 months lub 12 months. Apply HSTS policy to subdomains włącz dopiero po sprawdzeniu subdomen; Preload zostaw wyłączone, chyba że naprawdę tego chcesz.
  4. Save.

Cloudflare dodaje nagłówek do każdej odpowiedzi, którą serwuje. Jeśli Twój serwer też go wysyła, sprawdź curl, czy wynikiem jest jeden nagłówek z wartością, którą chcesz.

Node.js i Next.js

Next.js (własny hosting), w next.config.js:

module.exports = {
    async headers() {
        return [{
            source: '/:path*',
            headers: [
                { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
            ],
        }];
    },
};

Na Vercelu własne domeny dostają nagłówek HSTS od platformy; zanim dodasz własny, sprawdź curl.

Express: pakiet helmet domyślnie wysyła Strict-Transport-Security z rocznym max-age (app.use(helmet())). Jeśli przed aplikacją stoi nginx, wystarczy ustawienie tam (zakładka nginx).

Laravel

Utwórz middleware, na przykład app/Http/Middleware/StrictTransportSecurity.php:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class StrictTransportSecurity
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        if ($request->isSecure()) {
            $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
        }

        return $response;
    }
}

Zarejestruj go w bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\App\Http\Middleware\StrictTransportSecurity::class);
})

Za load balancerem lub proxy isSecure() jest prawdziwe tylko wtedy, gdy Laravel ufa proxy ($middleware->trustProxies(at: '*') lub adres IP Twojego proxy). Inaczej nagłówek nigdy się nie pojawi.

IIS i ASP.NET

W pliku web.config strony:

<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

W ASP.NET Core app.UseHsts() domyślnie wysyła tylko 30 dni, czyli mniej niż 180 wymagane przez listę kontrolną. Ustaw wiek jawnie:

builder.Services.AddHsts(options =>
{
    options.MaxAge = TimeSpan.FromDays(365);
    options.IncludeSubDomains = true;
});

Platformy hostowane

Na Shopify, Wix, Squarespace i Webflow nagłówki odpowiedzi ustawia platforma. Jeśli HSTS brakuje albo jest krótszy niż 180 dni, po Twojej stronie nie ma na to ustawienia; możesz zapytać wsparcie platformy albo postawić stronę za Cloudflare i ustawić go tam.

Potwierdź poprawkę

Uruchom ponownie polecenia curl z sekcji Sprawdź sam, potem otwórz cel w PulseKeeper i kliknij Sprawdź teraz na zakładce Lista kontrolna. Pozycja pokazuje znalezione max-age w dniach.