Bagaimana cara memaksa atau mengarahkan ulang ke SSL di nginx?


222

Saya memiliki halaman pendaftaran di subdomain seperti: https://signup.example.com

Seharusnya hanya dapat diakses melalui HTTPS tapi saya khawatir orang akan tersandung melalui HTTP dan mendapatkan 404.

Blok html / server saya di nginx terlihat seperti ini:

html {
  server {
    listen 443;
    server_name signup.example.com;

    ssl                        on;
    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    ssl_session_timeout 30m;

    location / {
      root /path/to/my/rails/app/public;
      index index.html;
        passenger_enabled on;
    }
  }
}

Apa yang bisa saya tambahkan sehingga orang yang pergi untuk http://signup.example.comdiarahkan https://signup.example.com? (FYI saya tahu ada plugin Rails yang bisa memaksa SSLtetapi berharap untuk menghindari itu)


Jawaban:


145

Menurut perangkap nginx , sedikit lebih baik untuk menghilangkan tangkapan yang tidak perlu, gunakan $request_urisaja. Dalam hal itu, tambahkan tanda tanya untuk mencegah nginx menggandakan argumen permintaan apa pun.

server {
    listen      80;
    server_name signup.mysite.com;
    rewrite     ^   https://$server_name$request_uri? permanent;
}

68
Atau, menurut situs yang Anda return 301 http://domain.com$request_uri;
tautkan

13
satu komentar. $ server_name $ mengambil variabel server_name pertama. Jadi waspadai ini jika Anda memiliki nama non FQN dalam konfigurasi Anda
engineerDave

2
@ nh2 Ini adalah kasus lain dari dokumentasi yang salah karena menggunakan return 301...menyebabkan kesalahan "terlalu banyak pengalihan" sementara metode penulisan ulang benar-benar berfungsi.
Mike Bethany

1
Itu sekarang didokumentasikan sebagai "juga BURUK". @MikeBethany return 301tidak berfungsi, kecuali (saya kira) Anda memicu juga untuk URL yang benar, dengan mendengarkan pada kedua port (contoh konfigurasi memicu masalah: ambil serverfault.com/a/474345/29689's jawaban pertama dan hilangkan jika ).
Blaisorblade

1
Saya bertanya-tanya apa yang telah berubah selama bertahun-tahun dan apakah jawaban lain ini lebih baik: serverfault.com/a/337893/119666
Ryan

256

Cara terbaik seperti yang dijelaskan dalam petunjuk resmi adalah dengan menggunakan returnarahan:

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

5
jawaban terpendek dan bekerja dengan sempurna dalam kasus saya
mateusz.fiolka

1
Ini umumnya disarankan karena mengembalikan 301 Moved Permanently(tautan Anda telah dipindahkan secara permanen) dan juga penulisan ulang
sgb

1
Ini tidak berfungsi karena menyebabkan kesalahan "terlalu banyak arahan ulang" walaupun Anda telah menyetelproxy_set_header X-Forwarded-Proto https;
Mike Bethany

1
@ MikeBethany, apakah Anda mendefinisikan listen 443;di blok yang sama?
Joe B

2
Ini harus menjadi jawaban yang diterima.
sjas

119

Ini adalah cara yang benar dan paling efisien jika Anda ingin menyimpannya dalam satu blok server:

server {
    listen   80;
    listen   [::]:80;
    listen   443 default_server ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }
}

Semua yang lain di atas, menggunakan "menulis ulang" atau "jika ssl_protocol" dll lebih lambat dan lebih buruk.

Berikut ini adalah sama, tetapi bahkan lebih efisien, dengan hanya menjalankan penulisan ulang pada protokol http ia menghindari harus memeriksa variabel $ skema pada setiap permintaan. Tapi serius, itu hal kecil sehingga Anda tidak perlu memisahkan mereka.

server {
    listen   80;
    listen   [::]:80;

    server_name www.example.com;

    return 301 https://$server_name$request_uri;
}
server {
    listen   443 default_server ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;
}

8
Hebat, beberapa pengecut memilih jawaban ini tanpa mengatakan mengapa, meskipun jawaban ini benar. Mungkin salah satu dari sekte "jika itu jahat" itu. Jika Anda bersusah payah membaca dokumentasi Nginx tentang If, Anda akan tahu bahwa IfIsNOTEvil, hanya TERTENTU menggunakannya dalam konteks lokasi {}, tidak ada yang kami lakukan di sini. Jawaban saya benar-benar cara yang benar dalam melakukan sesuatu!
DELETEDACC

2
Saya tidak memilih ini, tetapi saya ingin menunjukkan bahwa default telah diubah menjadi 'default_server' di versi terbaru.
Pengamen

Solusi pertama tidak bisa menjadi yang paling efisien, jika yang kedua bahkan lebih efisien. Dan Anda bahkan menjelaskan, mengapa Anda tidak harus menggunakan if jika ada: "itu harus memeriksa variabel $ skema pada setiap permintaan". Maksud dari tidak menggunakan seandainya bukan hanya tentang kinerja, tetapi juga tentang menjadi deklaratif, dan bukan keharusan.
pepkin88

+1 untuk if ($ skema = http)
Fernando Kosh

Harus menggunakan $ host di sini, sebagaimana disebutkan dalam jawaban lain.
Artem Russakovskii

56

Jika Anda menggunakan definisi server HTTP ganda dan HTTPS baru, Anda dapat menggunakan yang berikut:

server {
    listen   80;
    listen   [::]:80;
    listen   443 default ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    if ($ssl_protocol = "") {
       rewrite ^   https://$server_name$request_uri? permanent;
    }
}

Ini tampaknya bekerja untuk saya dan tidak menyebabkan pengalihan loop.

Sunting:

Diganti:

rewrite ^/(.*) https://$server_name/$1 permanent;

dengan garis penulisan ulang Pratik.


2
@ DavidvidPashley solusi Anda bekerja seperti pesona bagi saya. Terima kasih
Jayesh Gopalan

1
If you are using the new dual HTTP and HTTPS server definitionmaka Anda harus memisahkannya.
VBart

2
elegan dan bekerja sempurna!
jacktrade

2
Ini adalah satu-satunya solusi yang bekerja untuk saya dengan konfigurasi Laravel / Homestead Nginx saya.
Jared Eitnier

1
Baris penulisan ulang seharusnya juga return 301 https://$server_name$request_uri;karena ini adalah metode yang disukai.
Jared Eitnier

27

Namun varian lain, yang mempertahankan Host: header permintaan dan mengikuti contoh "BAIK" pada perangkap nginx :

server {
    listen   10.0.0.134:80 default_server;

    server_name  site1;
    server_name  site2;
    server_name  10.0.0.134;

    return 301 https://$host$request_uri;
}

Inilah hasilnya. Perhatikan bahwa menggunakan $server_namealih-alih $hostakan selalu dialihkan ke https://site1.

# curl -Is http://site1/ | grep Location
Location: https://site1/

# curl -Is http://site2/ | grep Location
Location: https://site2/


# curl -Is http://site1/foo/bar | grep Location
Location: https://site1/foo/bar

# curl -Is http://site1/foo/bar?baz=qux | grep Location
Location: https://site1/foo/bar?baz=qux

Note that using $server_name instead of $host would always redirect to https://site1bukankah itu untuk apa $request_uri?
Jürgen Paul

2
$request_uritidak mengandung nama host atau domain. Dengan kata lain, selalu dimulai dengan karakter "/".
Peter

2
Jawaban terbaik sejauh ini.
Ashesh 3-15

3
Saya tidak yakin mengapa jawaban ini sangat rendah. Ini satu-satunya yang layak digunakan.
zopieux

2
Tidak percaya begitu banyak orang menggunakan $ server_name ini adalah cara yang benar untuk melakukannya
Greg Ennis

3

Pastikan Anda mengatur 'aman' pada cookie apa pun, atau cookie itu akan dikirim pada permintaan HTTP dan bisa diambil oleh alat seperti Firesheep.


1
server {
    listen x.x.x.x:80;

    server_name domain.tld;
    server_name www.domian.tld;
    server_name ipv4.domain.tld;

    rewrite     ^   https://$server_name$request_uri? permanent;
}

Ini bekerja lebih baik menurut saya. xxxx merujuk ke IP server Anda. Jika Anda bekerja dengan Plesk 12, Anda dapat melakukannya dengan mengubah file "nginx.conf" di direktori "/var/www/vhosts/system/domain.tld/conf" untuk domain apa pun yang Anda inginkan. Jangan lupa untuk me-restart layanan nginx setelah Anda menyimpan konfigurasi.


rewrite ^ https://$host$request_uri? permanent; akan menjadi solusi yang lebih baik karena Anda mungkin memiliki beberapa nama server di vhost

0

Saya pikir ini adalah solusi paling sederhana. Memaksa lalu lintas non-HTTPS dan non-WWW hanya ke HTTPS dan www.

server {
    listen 80;
    listen 443 ssl;

    server_name domain.tld www.domain.tld;

    # global HTTP handler
    if ($scheme = http) {
        return 301 https://www.domain.tld$request_uri;
    }

    # global non-WWW HTTPS handler
    if ($http_host = domain.tld) {
        return 303 https://www.domain.tld$request_uri;
    }
}

EDIT - Apr 2018: Solusi tanpa IF dapat ditemukan di posting saya di sini: https://stackoverflow.com/a/36777526/6076984


1
Bukankah JIKA kondisi dianggap jahat dan tidak efisien di dunia nginx?
PKHunter

Ya, secara umum. Tetapi untuk pemeriksaan sederhana ini saya rasa tidak. Saya memang memiliki file konfigurasi yang tepat yang melibatkan penulisan kode lebih banyak, tetapi menghindari IF sepenuhnya.
stamster

Google menyarankan untuk menggunakan 301 daripada 303. Sumber: support.google.com/webmasters/answer/6073543?hl=id
dylanh724

@DylanHunt - Saya meninggalkan 303 hanya untuk pengujian, perhatikan bahwa penangan pertama diatur ke 301, hanya ke-2 saya lupa untuk mengubah :) Juga, solusi tanpa-JIKA: stackoverflow.com/a/36777526/6076984
stamster
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.