Làm cách nào để buộc hoặc chuyển hướng đến SSL trong nginx?


222

Tôi có một trang đăng ký trên một tên miền phụ như: https://signup.example.com

Nó chỉ có thể được truy cập thông qua HTTPS nhưng tôi lo lắng mọi người có thể vấp phải nó qua HTTP và nhận được 404.

Khối html / máy chủ của tôi trong nginx trông như thế này:

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;
    }
  }
}

Tôi có thể thêm gì để những người đi http://signup.example.comchuyển hướng đến https://signup.example.com? (FYI tôi biết có các plugin Rails có thể ép buộc SSLnhưng hy vọng tránh điều đó)


Câu trả lời:


145

Theo cạm bẫy nginx , tốt hơn là bỏ qua việc chụp không cần thiết, sử dụng $request_urithay thế. Trong trường hợp đó, hãy thêm một dấu hỏi để ngăn nginx nhân đôi bất kỳ đối số truy vấn nào.

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

68
Hoặc, theo trang web bạn đã liên kết, "TỐT HƠN" :return 301 http://domain.com$request_uri;
nh2

13
Một bình luận. $ server_name $ chọn biến server_name đầu tiên. Vì vậy, hãy lưu ý điều này nếu bạn có tên không phải FQN trong cấu hình của mình
EngineeringDave

2
@ nh2 Đây là một trường hợp khác của tài liệu bị sai do sử dụng return 301...gây ra lỗi "quá nhiều chuyển hướng" trong khi phương thức viết lại thực sự hoạt động.
Mike Bethany

1
Điều đó bây giờ được ghi nhận là "cũng BAD". @MikeBethany return 301làm việc, trừ khi (tôi đoán), bạn có đang hiển thị nó cũng cho URL đúng, bằng cách lắng nghe trên cả hai cổng (ví dụ về cấu hình kích hoạt các vấn đề:. Mất serverfault.com/a/474345/29689's câu trả lời đầu tiên và bỏ qua nếu ).
Blaisorblade

1
Tôi tự hỏi điều gì đã thay đổi trong những năm qua và liệu câu trả lời khác này có tốt hơn không: serverfault.com/a/337893/119666
Ryan

256

Cách tốt nhất như được mô tả trong hướng dẫn chính thức là sử dụng returnchỉ thị:

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

5
câu trả lời ngắn nhất và hoạt động hoàn hảo trong trường hợp của tôi
mateusz.fiolka

1
Điều này thường được đề xuất vì nó trả về một 301 Moved Permanently(các liên kết của bạn đã được di chuyển vĩnh viễn) cũng như viết lại
sgb

1
Điều này không hoạt động vì nó gây ra lỗi "quá nhiều chuyển hướng" ngay cả khi bạn đã đặtproxy_set_header X-Forwarded-Proto https;
Mike Bethany

1
@MikeBethany bạn đang xác định listen 443;trong cùng một khối?
Joe B

2
Đây phải là câu trả lời được chấp nhận.
sjas

119

Đây là cách chính xác và hiệu quả nhất nếu bạn muốn giữ tất cả trong một khối máy chủ:

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;
    }
}

Mọi thứ khác ở trên, sử dụng "viết lại" hoặc "nếu ssl_protatio", v.v ... chậm hơn và tệ hơn.

Đây là như vậy, nhưng thậm chí hiệu quả hơn, bằng cách chỉ chạy viết lại trên giao thức http, nó tránh được việc phải kiểm tra biến $ lược đồ trên mỗi yêu cầu. Nhưng nghiêm túc, đó là một điều nhỏ mà bạn không cần phải tách chúng ra.

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
Tuyệt vời, một số kẻ hèn nhát đã bỏ phiếu trả lời này mà không nói lý do tại sao, mặc dù câu trả lời này là chính xác. Có lẽ một trong những người sùng bái "nếu là xấu xa". Nếu bạn bận tâm đọc tài liệu Nginx về If, bạn sẽ biết rằng IfIsNOTEvil, chỉ CERTAIN sử dụng nó trong bối cảnh {}, không ai trong số chúng tôi làm ở đây. Câu trả lời của tôi hoàn toàn là cách làm đúng!
DELETEDACC

2
Tôi đã không bỏ phiếu này, nhưng tôi muốn chỉ ra rằng mặc định đã được thay đổi thành 'default_server' trong các phiên bản gần đây nhất.
kẻ lừa đảo

Giải pháp đầu tiên không thể hiệu quả nhất, nếu giải pháp thứ hai thậm chí còn hiệu quả hơn. Và bạn thậm chí đã mô tả, tại sao bạn không nên sử dụng nếu có: "nó tránh phải kiểm tra biến sơ đồ $ trên mỗi yêu cầu". Quan điểm không sử dụng ifs không chỉ là về hiệu năng, mà còn về việc khai báo, và không bắt buộc.
pepkin88

+1 cho if ($ Lược đồ = http)
Fernando Kosh

Nên sử dụng $ host ở đây, như đã đề cập trong các câu trả lời khác.
Artem Russakovskii

56

Nếu bạn đang sử dụng định nghĩa máy chủ HTTP và HTTPS kép mới, bạn có thể sử dụng như sau:

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;
    }
}

Điều này dường như làm việc cho tôi và không gây ra vòng lặp chuyển hướng.

Biên tập:

Thay thế:

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

với dòng viết lại của Pratik.


2
@DavidPashley giải pháp của bạn làm việc như một cơ duyên đối với tôi. Cảm ơn
Jayesh Gopalan

1
If you are using the new dual HTTP and HTTPS server definitionsau đó bạn nên tách nó ra
VBart

2
thanh lịch và hoạt động hoàn hảo!
jacktrade

2
Đây là giải pháp duy nhất phù hợp với tôi với cấu hình Laravel / Homestead Nginx.
Jared Eitnier

1
Ngoài ra, dòng viết lại phải là return 301 https://$server_name$request_uri;vì đây là phương pháp ưa thích.
Jared Eitnier

27

Một biến thể khác, bảo tồn tiêu đề Host: request và theo ví dụ "TỐT" trên các cạm bẫy 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;
}

Đây là kết quả. Lưu ý rằng sử dụng $server_namethay vì $hostluôn luôn chuyển hướng đến 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://site1đó không phải $request_urilà để làm gì?
Jürgen Paul

2
$request_urikhông chứa máy chủ hoặc tên miền. Nói cách khác, nó luôn bắt đầu bằng ký tự "/".
Peter

2
Câu trả lời tốt nhất cho đến nay.
Ashesh

3
Tôi không chắc tại sao câu trả lời này lại có số phiếu thấp như vậy. Đó là thứ duy nhất đáng để sử dụng.
zopieux

2
Cant tin rằng rất nhiều người sử dụng $ server_name, đây là cách chính xác để làm điều đó
Greg Enni

3

Đảm bảo bạn đặt 'an toàn' trên bất kỳ cookie nào, nếu không chúng sẽ được gửi theo yêu cầu HTTP và có thể được lấy bởi một công cụ như 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;
}

Điều này hoạt động tốt hơn tôi nghĩ. xxxx đề cập đến IP của máy chủ của bạn. Nếu bạn đang làm việc với Plesk 12, bạn có thể làm điều đó bằng cách thay đổi tệp "nginx.conf" trong thư mục "/var/www/vhosts/system/domain.tld/conf" cho bất kỳ tên miền nào bạn muốn. Đừng quên khởi động lại dịch vụ nginx sau khi bạn lưu cấu hình.


rewrite ^ https://$host$request_uri? permanent; sẽ là một giải pháp tốt hơn vì bạn có thể có một số tên máy chủ trên vhost

0

Tôi nghĩ rằng đây là giải pháp đơn giản nhất. Buộc cả lưu lượng truy cập không phải HTTPS và không WWW vào HTTPS và 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 - Tháng 4 năm 2018: Có thể tìm thấy giải pháp trong bài viết của tôi tại đây: https://stackoverflow.com/a/36777526/6076984


1
Không phải điều kiện IF được coi là xấu xa và không hiệu quả trong thế giới nginx?
PKHunter

Vâng, họ nói chung. Nhưng đối với kiểm tra đơn giản này, tôi sẽ đoán không. Tôi có một tệp cấu hình phù hợp bao gồm viết mã nhiều hơn, nhưng tránh hoàn toàn IF.
stamster

Google khuyên bạn nên sử dụng 301 thay vì 303. Nguồn: support.google.com/webmasters
đá / 6053543? Hl = vi

@DylanHunt - Tôi rời 303 chỉ để thử nghiệm, có một lưu ý rằng xử lý 1 được thiết lập để 301, chỉ có 2 tôi quên thay đổi :) Ngoài ra, giải pháp w / o NẾU của: stackoverflow.com/a/36777526/6076984
stamster
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.