Di Nginx, bagaimana saya bisa menulis ulang semua permintaan http ke https sambil mempertahankan sub-domain?


509

Saya ingin menulis ulang semua permintaan http di server web saya menjadi permintaan https, saya mulai dengan yang berikut:

server {
    dengarkan 80;

    lokasi / {
      menulis ulang ^ (. *) https: //mysite.com$1 permanen;
    }
...


Satu Masalahnya adalah bahwa ini menghapus informasi subdomain (mis., Node1.mysite.com/folder), bagaimana saya bisa menulis ulang di atas untuk mengubah rute semuanya menjadi https dan mempertahankan sub-domain?


2
Harap pertimbangkan untuk memindahkan 'jawaban yang diterima' ke serverfault.com/a/171238/90758 . Itu yang benar.
olafure

Cukup gunakan $ server_name alih-alih hardcoded mysite.com
Fedir RYKHTIK

Jawaban:


749

Cara yang benar dalam versi nginx baru

Ternyata jawaban pertama saya untuk pertanyaan ini benar pada waktu tertentu, tetapi itu berubah menjadi jebakan lain - untuk tetap up to date silakan periksa Perpajakan penulisan ulang perangkap

Saya telah dikoreksi oleh banyak pengguna SE, jadi kredit diberikan kepada mereka, tetapi yang lebih penting, ini adalah kode yang benar:

server {
       listen         80;
       server_name    my.domain.com;
       return         301 https://$server_name$request_uri;
}

server {
       listen         443 ssl;
       server_name    my.domain.com;
       # add Strict-Transport-Security to prevent man in the middle attacks
       add_header Strict-Transport-Security "max-age=31536000" always; 

       [....]
}

3
Namun Anda harus melakukan ini pada domain per domain - tidak? Bagaimana jika Anda ingin menerapkannya ke setiap domain di server Anda?
JM4

30
@ JM4: jika Anda menggunakan $ host $ dalam penulisan ulang alih-alih server_name dan menambahkan default_server ke directive listen, itu akan bekerja untuk setiap domain di server Anda.
Klaas van Schelven

5
Penting untuk menyebutkan bahwa 301 disimpan dalam cache lokal Anda tanpa tanggal kedaluwarsa. Tidak terlalu berguna saat konfigurasi berubah
Trefex

9
@everyone Gunakan pengalihan 307 untuk mempertahankan konten POST.
Mahmoud Al-Qudsi

10
Perhatikan bahwa Anda harus menggunakan $hostalih-alih $server_namejika Anda menggunakan subdomain.
Lele

276

CATATAN: Cara terbaik untuk melakukan ini disediakan oleh https://serverfault.com/a/401632/3641 - tetapi diulangi di sini:

server {
    listen         80;
    return 301 https://$host$request_uri;
}

Dalam kasus yang paling sederhana, host Anda akan diperbaiki untuk menjadi layanan yang ingin Anda kirimi mereka - ini akan melakukan pengalihan ke browser dan URL browser akan diperbarui sesuai dengan itu.

Di bawah ini adalah jawaban sebelumnya, yang tidak efisien karena regex, 301 sederhana sangat bagus seperti yang ditunjukkan oleh @kmindi

Saya telah menggunakan nginx 0.8.39 ke atas, dan menggunakan yang berikut:

 server {
       listen 80;
       rewrite ^(.*) https://$host$1 permanent;
 }

Mengirim redirect permanen ke klien.


15
Saya pikir itu harus 80 - karena ini mendengarkan untuk http dan kemudian memberitahu klien untuk kembali sebagai https (443).
Michael Neale

3
Ini harus menjadi jawaban teratas!
Nathan

3
Ini adalah jawaban yang paling melelahkan.
Kasus

1
ini adalah yang termudah, tetapi paling tidak aman - dengan cara ini Anda mengizinkan server Anda untuk mengarahkan ulang pengguna ke halaman mana pun, tanpa memeriksa apakah itu bahkan diizinkan untuk digunakan di server Anda. Jika server Anda melayani mydomain.co, pengguna jahat masih dapat menggunakan server Anda untuk mengarahkan pengguna ke domain lain seperti mydomain.co, seperti google.com.
friedkiwi

10
@ cab0lt tidak ada masalah keamanan di sini. Melayani pengalihan tidak menghadirkan risiko keamanan. Jika ada persyaratan kontrol akses, itu harus diperiksa pada titik di mana browser meminta URL baru. Browser tidak akan mendapatkan akses hanya atas dasar pengalihan, mereka juga tidak memerlukan pengalihan untuk meminta URL baru.
mc0e

125

Saya pikir cara terbaik dan satu-satunya harus menggunakan HTTP 301 Moved redirect secara permanen seperti ini:

server {
    listen         [::]:80;
    return 301 https://$host$request_uri;
}

The HTTP 301 Moved Permanently redirect juga yang paling efisien karena tidak ada regex untuk dievaluasi, menurut yang telah disebutkan pitfails .


Baru HTTP 308 Moved Permanently mempertahankan metode Permintaan dan didukung oleh browser utama . Misalnya, menggunakan 308mencegah browser mengubah metode permintaan dari POSTmenjadi GETuntuk permintaan pengalihan.


Jika Anda ingin mempertahankan nama host dan subdomain, inilah caranya.

Ini masih berfungsi jika Anda tidak memiliki DNS , karena saya juga menggunakannya secara lokal. Saya meminta misalnya dengan http://192.168.0.100/index.phpdan akan diarahkan ke tepat https://192.168.0.100/index.php.

Saya menggunakan listen [::]:80pada host saya karena saya telah bindv6onlymenetapkan untuk false, sehingga juga mengikat socket ipv4. ubah ke listen 80jika Anda tidak ingin IPv6 atau ingin mengikat di tempat lain.

Solusi dari Saif Bechan menggunakan server_nameyang dalam kasus saya adalah localhost tetapi itu tidak dapat dijangkau melalui jaringan.

Solusi dari Michael Neale bagus, tetapi menurut pitfail, ada solusi yang lebih baik dengan redirect 301;)


Bagus Anda mencoba mengutipnya, tetapi 301 tidak bekerja pada HTTPS.
Kasus

5
apa yang tidak bekerja bagian server yang disebutkan adalah untuk lalu lintas http (tanpa s) yang tidak dienkripsi untuk secara permanen dialihkan ke server terenkripsi (bagian yang mendengarkan 443 (https) tidak terdaftar)
kmindi

Saya memeriksa ini berfungsi baik dengan https dan semuanya - @kmindi Saya memperbarui jawaban saya dengan merujuk pada Anda - karena saya pikir ini adalah cara yang benar dan ini terus bermunculan! Kerja bagus.
Michael Neale

Saat menggunakan permintaan domain (non-ip), tidak berfungsi kecuali jika saya mengubah '[::]: 80' menjadi '80'.
Joseph Lust

itu bisa menjadi perilaku yang diharapkan: trac.nginx.org/nginx/ticket/345 . Saya memperbarui jawaban untuk menjelaskan opsi mendengarkan.
kmindi

20

Dalam blok server Anda juga dapat melakukan hal berikut:

# Force HTTPS connection. This rules is domain agnostic
if ($scheme != "https") {
    rewrite ^ https://$host$uri permanent;
}

2
Konfigurasi ini menyebabkan server saya menghasilkan loop pengalihan
Corkscreewe

Mungkin karena ada pengalihan lain di tempat atau https tidak diaktifkan di situs / aplikasi Anda
Oriol

1
Tidak ada yang lain yang berfungsi kecuali yang ini. Menggunakan versi nginx: nginx / 1.10.0 (Ubuntu)
ThatGuy343

terunggah untuk https: // $ host $ uri
AMB

4
Ini adalah cara untuk pergi jika Anda berada di belakang loadbalancer!
Antwan

17

Di atas tidak berfungsi dengan subdomain baru yang dibuat setiap saat. misalnya AAA.example.com BBB.example.com untuk sekitar 30 subdomain.

Akhirnya ada konfigurasi yang bekerja dengan yang berikut:

server {
  listen 80;
  server_name _;
  rewrite ^ https://$host$request_uri? permanent;
}
server {
  listen  443;
  server_name example.com;
  ssl on;
  ssl_certificate /etc/ssl/certs/myssl.crt;
  ssl_certificate_key /etc/ssl/private/myssl.key;
  ssl_prefer_server_ciphers       on;
# ...
# rest of config here
# ...
}

Terima kasih! nginx akan mengembalikan 301 https://*/atau membatalkan permintaan sebelum waktunya di jawaban lain di sini. server_name _;dengan $hostadalah jawaban yang berhasil. +1
zamnuts

1
Yang ini optimal! Namun, saya menyarankan beberapa untuk mengganti _dengan domain yang sebenarnya, misalnya .domain.comsaya punya dua server, dan nginx secara tidak sengaja mengarahkan salah satu server saya ke server default.
zzz

1
Ini satu-satunya jawaban yang berhasil buat saya, terima kasih!
Snowman

Terima kasih banyak sobat .. Saya telah mencoba banyak solusi tetapi tidak berhasil. Solusi ini luar biasa dan berhasil bagi saya. nama server _; apa artinya ini untuk .. Saya tidak mengerti. Tolong jelaskan ini pada saya.
Pavan Kumar

6

Saya mengirim komentar pada jawaban yang benar sejak dulu, dengan koreksi yang sangat penting, tetapi saya merasa perlu untuk menyoroti koreksi ini dalam jawabannya sendiri. Tidak satu pun dari jawaban sebelumnya yang aman digunakan jika pada suatu saat Anda memiliki HTTP yang tidak aman diatur dan mengharapkan konten pengguna, memiliki formulir, host API, atau telah mengkonfigurasi situs web, alat, aplikasi, atau utilitas untuk berbicara ke situs Anda.

Masalah terjadi ketika POSTpermintaan dibuat ke server Anda. Jika respons server dengan 30xpengalihan polos , konten POST akan hilang. Apa yang terjadi adalah bahwa browser / klien akan meningkatkan permintaan untuk SSL tapi downgrade yang POSTke GETpermintaan. The POSTparameter akan hilang dan permintaan yang salah akan dibuat ke server Anda.

Solusinya sederhana. Anda perlu menggunakan HTTP 1.1 307arahan ulang. Ini dirinci dalam RFC 7231 S6.4.7:

  Note: This status code is similar to 302 (Found), except that it
  does not allow changing the request method from POST to GET.  This
  specification defines no equivalent counterpart for 301 (Moved
  Permanently) ([RFC7238], however, defines the status code 308
  (Permanent Redirect) for this purpose).

Solusinya, diadaptasi dari solusi yang diterima, adalah untuk digunakan 307dalam kode pengalihan Anda:

server {
       listen         80;
       server_name    my.domain.com;
       return         307 https://$server_name$request_uri;
}

server {
       listen         443 ssl;
       server_name    my.domain.com;
       # add Strict-Transport-Security to prevent man in the middle attacks
       add_header Strict-Transport-Security "max-age=31536000"; 

       [....]
}


4

Saya menjalankan ngnix di belakang AWS ELB. ELB sedang berbicara dengan ngnix melalui http. Karena ELB tidak memiliki cara untuk mengirim arahan ulang ke klien, saya memeriksa header X-Forwarded-Proto dan mengarahkan ulang:

if ($http_x_forwarded_proto != 'https') {
    return 301 "https://www.exampl.com";
}

1

Jika Anda return 301 https://$host$request_uri;sebagai respons default pada port 80, maka server Anda cepat atau lambat akan masuk dalam daftar proxy terbuka [1] dan mulai disalahgunakan untuk mengirim lalu lintas ke tempat lain di Internet. Jika log Anda diisi dengan pesan seperti ini, maka Anda tahu itu terjadi pada Anda:

42.232.104.114 - - [25/Mar/2018:04:50:49 +0000] "GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1" 301 185 "http://www.ioffer.com/" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Hotbar 4.1.8.0; RogueCleaner; Alexa Toolbar)"

Masalahnya adalah bahwa $hostakan mengulang kembali apa pun yang dikirim browser di Hostheader atau bahkan nama host dari baris pembuka HTTP, seperti ini:

GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1

Karena masalah itu, beberapa jawaban lain di sini merekomendasikan menggunakan $server_namebukan $host. $server_nameselalu mengevaluasi apa yang Anda masukkan dalam server_namedeklarasi. Tetapi jika Anda memiliki beberapa subdomain di sana atau menggunakan wildcard, itu tidak akan berhasil, karena $server_namehanya menggunakan entri pertama setelah server_namedeklarasi, dan yang lebih penting hanya akan menggemakan kembali wildcard (tidak memperluasnya).

Jadi bagaimana cara mendukung banyak domain sambil menjaga keamanan? Pada sistem saya sendiri, saya telah menangani dilema ini dengan terlebih dahulu mendaftar default_serverblok yang tidak digunakan $host, dan kemudian daftar blok wildcard yang:

server {
  listen 80 default_server;
  server_name example.com;
  return 301 https://example.com$request_uri;
}
server {
  listen 80;
  server_name *.example.com;
  return 301 https://$host$request_uri;
}

(Anda juga bisa mencantumkan lebih dari satu domain di blok kedua.)

Dengan kombinasi itu, domain yang tidak cocok akan dialihkan ke tempat yang di-hardcode (selalu example.com), dan domain yang cocok dengan domain Anda akan menuju ke tempat yang tepat. Server Anda tidak akan berguna sebagai proxy terbuka, sehingga Anda tidak akan menarik masalah.

Jika Anda merasa sulit, saya kira Anda juga bisa membuat default_serverblok tidak cocok dengan domain Anda yang sah dan menyajikan sesuatu yang menyinggung. . . .

[1] Secara teknis "proxy" adalah kata yang salah, karena server Anda tidak keluar dan memenuhi permintaan untuk klien, hanya mengirim redirect, tetapi saya tidak yakin apa kata yang tepat. Saya juga tidak yakin apa tujuannya, tetapi mengisi log Anda dengan noise dan menghabiskan CPU dan bandwidth Anda, jadi sebaiknya Anda menghentikannya.


0

Sepertinya tidak ada yang benar-benar mendapatkannya 100% benar. Untuk meminta permintaan port 80 pergi ke 443 ekuivalennya untuk seluruh server web, Anda perlu menggunakan directive listen , bukan directive server_name untuk menentukan nama catch-all . Lihat juga https://nginx.org/en/docs/http/request_processing.html

server {
    dengarkan 80 default;
    dengarkan [::]: 80 default;
      kembali 307 https: // $ host $ request_uri;
}
  • $ host menangkap nama subdomain.
  • 307 dan 308 termasuk URI permintaan POST dan GET.
  • 307 bersifat sementara, ubah ke Permanen 308 setelah pengujian menyeluruh:

Dan pastikan Anda memeriksa apa yang sudah ada di /etc/nginx/conf.d/ karena lebih sering saya tidak memiliki masalah di mana default.conf mengembalikan beberapa vhost yang ada. Pesanan saya untuk mengatasi masalah nginx selalu dimulai dengan memindahkan file default, mengembalikannya mengomentari baris demi baris untuk melihat kesalahannya.


-1
rewrite ^!https https://$host$request_uri permanent;
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.