วิธีการบังคับหรือเปลี่ยนเส้นทางไปยัง SSL ใน nginx


222

ฉันมีหน้าลงทะเบียนในโดเมนย่อยเช่น: https://signup.example.com

มันควรจะสามารถเข้าถึงได้ผ่าน HTTPS แต่ฉันกังวลว่าผู้คนอาจสะดุดมันผ่าน HTTP และรับ 404

html / server block ของฉันใน nginx มีลักษณะดังนี้:

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

สิ่งที่ฉันสามารถเพิ่มเพื่อให้ผู้คนที่ไปhttp://signup.example.comได้รับการเปลี่ยนเส้นทางไปยังhttps://signup.example.com? (FYI ฉันรู้ว่ามีปลั๊กอิน Rails ที่สามารถบังคับได้SSLแต่หวังว่าจะหลีกเลี่ยงได้)


คำตอบ:


145

ตามข้อผิดพลาดของ nginxจะดีกว่าเล็กน้อยที่จะละเว้นการจับที่ไม่จำเป็นใช้$request_uriแทน ในกรณีดังกล่าวให้ใส่เครื่องหมายคำถามต่อท้ายเพื่อป้องกัน nginx ไม่ให้เพิ่มคิวรีแบบสอบถามใด ๆ เป็นสองเท่า

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

68
หรือตามเว็บไซต์ที่คุณเชื่อมโยง"ดีกว่า" :return 301 http://domain.com$request_uri;
nh2

13
หนึ่งความคิดเห็น $ server_name $ เลือกตัวแปร server_name ตัวแรก ดังนั้นจงระวังสิ่งนี้หากคุณไม่มีชื่อ FQN ในการกำหนดค่าของคุณ
engineerDave

2
@ nh2 นี่เป็นอีกกรณีของเอกสารที่ผิดเนื่องจากการใช้return 301...ทำให้เกิดข้อผิดพลาด "การเปลี่ยนเส้นทางมากเกินไป" ในขณะที่วิธีการเขียนใหม่ใช้งานได้จริง
Mike Bethany

1
ขณะนี้มีการบันทึกว่า "ยังไม่ดี" @MikeBethany return 301ไม่ทำงานเว้นแต่ (ฉันเดา) คุณเรียกมันยังสำหรับ URL ที่ถูกต้องโดยการฟังบนพอร์ตทั้ง (ตัวอย่างของการตั้งค่าวิกฤติปัญหา:. ใช้serverfault.com/a/474345/29689'sคำตอบแรกและละเว้นถ้า )
Blaisorblade

1
ฉันสงสัยว่ามีการเปลี่ยนแปลงอย่างไรในช่วงหลายปีที่ผ่านมาและคำตอบอื่น ๆ นี้ดีกว่าหรือไม่: serverfault.com/a/337893/119666
Ryan

256

วิธีที่ดีที่สุดตามที่อธิบายไว้ในวิธีการอย่างเป็นทางการคือการใช้returnคำสั่ง:

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

5
คำตอบที่สั้นที่สุดและทำงานได้อย่างสมบูรณ์แบบในกรณีของฉัน
mateusz.fiolka

1
แนะนำโดยทั่วไปเพราะจะส่งคืน301 Moved Permanently(ลิงก์ของคุณย้ายไปอย่างถาวร) รวมถึงการเขียนใหม่
sgb

1
วิธีนี้ใช้ไม่ได้เนื่องจากทำให้เกิดข้อผิดพลาด "การเปลี่ยนเส้นทางมากเกินไป" แม้ว่าคุณจะตั้งค่าไว้ก็ตามproxy_set_header X-Forwarded-Proto https;
Mike Bethany

1
@MikeBethany คุณกำหนดไว้listen 443;ในบล็อกเดียวกันหรือไม่
Joe B

2
นี่ควรเป็นคำตอบที่ยอมรับได้
sjas

119

นี่เป็นวิธีที่ถูกต้องและมีประสิทธิภาพมากที่สุดหากคุณต้องการเก็บไว้ในบล็อกเซิร์ฟเวอร์เดียว:

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

ทุกอย่างข้างต้นโดยใช้ "เขียนใหม่" หรือ "if ssl_protocol" ฯลฯ ช้าลงและแย่ลง

ที่นี่เหมือนกัน แต่มีประสิทธิภาพมากขึ้นโดยการรันการเขียนใหม่บนโปรโตคอล http เท่านั้นมันหลีกเลี่ยงการตรวจสอบตัวแปร $ scheme ในทุกคำขอ แต่อย่างจริงจังมันเป็นเรื่องเล็กน้อยที่คุณไม่จำเป็นต้องแยกพวกเขาออก

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
เยี่ยมมากคนขี้ขลาดบางคนโหวตคำตอบนี้โดยไม่บอกว่าทำไมถึงแม้ว่าคำตอบนี้ถูกต้อง อาจเป็นอีกหนึ่งในลัทธิ "ถ้าเป็นคนชั่ว" หากคุณอยากอ่านเอกสารเกี่ยวกับ Nginx เกี่ยวกับ If คุณจะรู้ว่า IfIsNOTEvil เพียง CERTAIN ใช้มันในบริบท {} บริบทซึ่งเราไม่ได้ทำที่นี่ คำตอบของฉันเป็นวิธีที่ถูกต้องในการทำสิ่งต่าง ๆ !
DELETEDACC

2
ฉันไม่ได้ลงคะแนน แต่ฉันต้องการชี้ให้เห็นว่าค่าเริ่มต้นถูกเปลี่ยนเป็น 'default_server' ในเวอร์ชันล่าสุด
spuder

ทางออกแรกไม่สามารถมีประสิทธิภาพมากที่สุดหากวิธีที่สองมีประสิทธิภาพมากขึ้น และคุณก็อธิบายว่าทำไมคุณไม่ควรใช้ถ้ามี: "มันหลีกเลี่ยงการตรวจสอบตัวแปร $ scheme ในทุกคำขอ" จุดที่ไม่ได้ใช้ ifs ไม่เพียง แต่เกี่ยวกับประสิทธิภาพ แต่ยังเกี่ยวกับการประกาศและไม่จำเป็น
pepkin88

+1 ด้วยถ้า ($ scheme = http)
Fernando Kosh

ควรใช้ $ host ที่นี่ตามที่ระบุไว้ในคำตอบอื่น ๆ
Artem Russakovskii

56

หากคุณกำลังใช้นิยามเซิร์ฟเวอร์ HTTP และ HTTPS คู่ใหม่คุณสามารถใช้สิ่งต่อไปนี้:

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

ดูเหมือนว่าจะใช้งานได้สำหรับฉันและไม่ทำให้เกิดการเปลี่ยนเส้นทาง

แก้ไข:

แทนที่:

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

ด้วยบรรทัดเขียนใหม่ของ Pratik


2
@DavidPashley โซลูชันของคุณใช้งานได้ดีกับฉัน ขอบคุณ
Jayesh Gopalan

1
If you are using the new dual HTTP and HTTPS server definitionจากนั้นคุณควรแยกมัน
VBart

2
สง่างามและใช้งานได้สมบูรณ์แบบ!
jacktrade

2
นี่เป็นทางออกเดียวที่ทำงานกับฉันด้วยการกำหนดค่า Laravel / Homestead Nginx ของฉัน
Jared Eitnier

1
นอกจากนี้บรรทัดการเขียนควรเป็นreturn 301 https://$server_name$request_uri;เช่นนี้เป็นวิธีที่ต้องการ
Jared Eitnier

27

อีกตัวแปรหนึ่งที่เก็บรักษา Host: request header และปฏิบัติตามตัวอย่าง "GOOD" บนข้อผิดพลาด 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;
}

นี่คือผลลัพธ์ โปรดทราบว่าใช้$server_nameแทนมักจะเปลี่ยนเส้นทางไปยัง$hosthttps://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ไม่ใช่$request_uriเพื่ออะไร
Jürgen Paul

2
$request_uriไม่มีชื่อโฮสต์หรือโดเมน กล่าวอีกนัยหนึ่งมันเริ่มต้นด้วยอักขระ "/" เสมอ
ปีเตอร์

2
คำตอบที่ดีที่สุดโดยไกล
Ashesh

3
ฉันไม่แน่ใจว่าทำไมคำตอบนี้จึงต่ำมาก มันเป็นสิ่งเดียวที่ควรค่าแก่การใช้
zopieux

2
ลาดเทเชื่อว่าผู้คนจำนวนมากใช้ $ server_name นี้เป็นวิธีที่ถูกต้องที่จะทำมัน
เกร็กเอนนิส

3

ตรวจสอบให้แน่ใจว่าคุณได้ตั้งค่า 'ความปลอดภัย' บนคุกกี้ใด ๆ มิฉะนั้นจะถูกส่งไปตามคำขอ HTTP และสามารถใช้เครื่องมือเช่น 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;
}

มันใช้งานได้ดีกว่าที่ฉันคิด xxxx หมายถึง IP ของเซิร์ฟเวอร์ของคุณ หากคุณทำงานกับ Plesk 12 คุณสามารถทำได้โดยการเปลี่ยนไฟล์ "nginx.conf" ในไดเรกทอรี "/var/www/vhosts/system/domain.tld/conf" สำหรับโดเมนที่คุณต้องการ อย่าลืมรีสตาร์ทเซอร์วิส nginx หลังจากคุณบันทึกการกำหนดค่า


rewrite ^ https://$host$request_uri? permanent; จะเป็นวิธีแก้ปัญหาที่ดีกว่าเนื่องจากคุณอาจมีชื่อเซิร์ฟเวอร์หลายชื่อใน vhost

0

ฉันคิดว่านี่เป็นทางออกที่ง่ายที่สุด บังคับใช้ทั้งทราฟฟิกที่ไม่ใช่ HTTPS และที่ไม่ใช่ WWW ไปยัง HTTPS และ 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;
    }
}

แก้ไข - เม.ย. 2561: ทางออกที่ไม่มีหากพบได้ในโพสต์ของฉันที่นี่: https://stackoverflow.com/a/36777526/6076984


1
ถ้าหากเงื่อนไขถือว่าความชั่วร้ายและไม่มีประสิทธิภาพในโลก nginx?
PKHunter

ใช่พวกเขาโดยทั่วไป แต่สำหรับการตรวจสอบอย่างง่ายนี้ฉันจะเดาไม่ได้ ฉันมีไฟล์การกำหนดค่าที่เหมาะสมซึ่งเกี่ยวข้องกับการเขียนโค้ดเพิ่มเติม แต่หลีกเลี่ยงการใช้ IF ทั้งหมด
ต๊อคเกอร์

Google แนะนำให้ใช้ 301 แทน 303 ที่มา: support.google.com/webmasters/answer/6073543?hl=th
dylanh724

@DylanHunt - ฉันเหลือ 303 สำหรับการทดสอบเท่านั้นโปรดทราบว่าตัวจัดการที่ 1 ถูกตั้งค่าเป็น 301 เพียงที่สองฉันลืมที่จะเปลี่ยน :) นอกจากนี้ยังแก้ปัญหาโดยไม่ต้อง IF ของ: stackoverflow.com/a/36777526/6076984
เดินทอดน่อง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.