ใน Nginx ฉันจะเขียนคำขอ http ทั้งหมดไปยัง https ใหม่อีกครั้งในขณะที่ดูแลโดเมนย่อยได้อย่างไร


508

ฉันต้องการเขียนคำขอ http ทั้งหมดบนเว็บเซิร์ฟเวอร์ของฉันใหม่ให้เป็นคำขอ https ฉันเริ่มต้นด้วยสิ่งต่อไปนี้:

เซิร์ฟเวอร์ {
    ฟัง 80;

    ตำแหน่ง / {
      เขียนใหม่ ^ (. *) https: //mysite.com$1 ถาวร
    }
...


ปัญหาหนึ่งคือว่าสิ่งนี้จะลบข้อมูลโดเมนย่อยใด ๆ (เช่น node1.mysite.com/folder) ฉันจะเขียนข้างต้นอีกครั้งเพื่อเปลี่ยนเส้นทางทุกสิ่งไปยัง https และดูแลโดเมนย่อยได้อย่างไร


2
โปรดพิจารณาย้าย 'ตอบรับ' เพื่อserverfault.com/a/171238/90758 นั่นคือสิ่งที่ถูกต้อง
olafure

เพียงใช้ $ server_name แทน hardcoded mysite.com
Fedir RYKHTIK

คำตอบ:


748

วิธีที่ถูกต้องใน nginx เวอร์ชันใหม่

เปิดคำตอบแรกของฉันสำหรับคำถามนี้ถูกต้องในบางช่วงเวลา แต่มันกลับกลายเป็นข้อผิดพลาดอื่น ๆ - เพื่อให้ทันสมัย

ฉันได้รับการแก้ไขโดยผู้ใช้ SE หลายคนดังนั้นเครดิตจะเข้าสู่พวกเขา แต่ที่สำคัญกว่านั้นคือรหัสที่ถูกต้อง:

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
คุณจะต้องทำสิ่งนี้บนโดเมนตามโดเมน - ไม่ใช่? ถ้าคุณต้องการใช้กับทุกโดเมนบนเซิร์ฟเวอร์ของคุณ
JM4

30
@ JM4: ถ้าคุณใช้ $ host $ ในการเขียนแทน server_name และเพิ่ม default_server ไปยังฟัง directive มันจะใช้ได้กับทุกโดเมนบนเซิร์ฟเวอร์ของคุณ
Klaas van Schelven

5
เป็นสิ่งสำคัญที่จะกล่าวถึงว่า 301 ถูกเก็บไว้ในแคชในเครื่องของคุณโดยไม่มีวันหมดอายุ ไม่มีประโยชน์มากเมื่อเปลี่ยนแปลงการกำหนดค่า
Trefex

9
@everyone ใช้การเปลี่ยนเส้นทาง 307 เพื่อเก็บรักษาเนื้อหาของ POST
Mahmoud Al-Qudsi

10
โปรดทราบว่าคุณต้องใช้$hostแทน$server_nameหากคุณใช้โดเมนย่อย
ปลาดุก

276

หมายเหตุ: วิธีที่ดีที่สุดในการทำสิ่งนี้มีให้โดยhttps://serverfault.com/a/401632/3641 - แต่ทำซ้ำที่นี่:

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

ในกรณีที่ง่ายที่สุดโฮสต์ของคุณจะได้รับการแก้ไขให้เป็นบริการของคุณที่คุณต้องการส่งไปให้ - ซึ่งจะเป็นการเปลี่ยนเส้นทาง 301 ไปยังเบราว์เซอร์และ URL ของเบราว์เซอร์จะอัปเดตตามนั้น

ด้านล่างนี้เป็นคำตอบก่อนหน้านี้ซึ่งไม่มีประสิทธิภาพเนื่องจาก regex 301 อันง่ายดีมากดังที่แสดงโดย @kmindi

ฉันใช้ nginx 0.8.39 ขึ้นไปและใช้สิ่งต่อไปนี้:

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

ส่งการเปลี่ยนเส้นทางถาวรไปยังลูกค้า


15
ฉันคิดว่ามันควรจะ 80 - เพราะนี่คือการฟัง http และบอกให้ลูกค้ากลับมาเป็น https (443)
Michael Neale

3
นี่ควรเป็นคำตอบที่ดีที่สุด!
นาธาน

3
นี่คือคำตอบที่ต้องเสียภาษีมากที่สุด
กรณี

1
นี่เป็นวิธีที่ง่ายที่สุด แต่มีความปลอดภัยน้อยที่สุด - วิธีนี้ทำให้คุณอนุญาตให้เซิร์ฟเวอร์เปลี่ยนเส้นทางผู้ใช้ไปยังหน้าใดก็ได้โดยไม่ตรวจสอบว่าได้รับอนุญาตให้ใช้บนเซิร์ฟเวอร์ของคุณหรือไม่ หากเซิร์ฟเวอร์ของคุณให้บริการ mydomain.co ผู้ใช้ที่ประสงค์ร้ายยังคงสามารถใช้เซิร์ฟเวอร์ของคุณเพื่อเปลี่ยนเส้นทางผู้ใช้ไปยังโดเมนอื่น ๆ เช่น mydomain.co เช่น google.com
Friedkiwi

10
@ cab0lt ไม่มีปัญหาด้านความปลอดภัยที่นี่ การเปลี่ยนเส้นทางไม่แสดงความเสี่ยงด้านความปลอดภัย หากมีข้อกำหนดการควบคุมการเข้าถึงควรตรวจสอบสิ่งเหล่านั้น ณ จุดที่เบราว์เซอร์ร้องขอ URL ใหม่ เบราว์เซอร์จะไม่สามารถเข้าถึงได้อย่างง่ายดายบนพื้นฐานของการเปลี่ยนเส้นทางและไม่จำเป็นต้องมีการเปลี่ยนเส้นทางเพื่อขอ URL ใหม่
mc0e

125

ฉันคิดว่าวิธีที่ดีที่สุดและควรใช้HTTP 301 Moved อย่างถาวรเปลี่ยนเส้นทางดังนี้:

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

HTTP 301 ย้ายถาวรเปลี่ยนเส้นทางยังมีประสิทธิภาพมากที่สุดเพราะมี regex ที่จะได้รับการประเมินไม่เป็นไปตามที่กล่าวแล้วpitfails


ใหม่HTTP 308 ย้ายถาวรแยมขอวิธีการและการสนับสนุนจากเบราว์เซอร์ที่สำคัญ ตัวอย่างเช่นการใช้308ป้องกันเบราว์เซอร์จากการเปลี่ยนวิธีการร้องขอจากPOSTการGETสำหรับการร้องขอการเปลี่ยนเส้นทาง


หากคุณต้องการรักษาชื่อโฮสต์และโดเมนย่อยนี่คือวิธีการ

วิธีนี้ยังคงใช้งานได้หากคุณไม่มี DNSเพราะฉันใช้อยู่ในเครื่อง ฉันกำลังร้องขอเช่นกับและจะได้รับการเปลี่ยนเส้นทางไปว่าhttp://192.168.0.100/index.phphttps://192.168.0.100/index.php

ฉันใช้listen [::]:80บนโฮสต์ของฉันเพราะฉันได้bindv6onlyตั้งค่าfalseดังนั้นมันจึงผูกกับซ็อกเก็ต ipv4 เปลี่ยนเป็นlisten 80หากคุณไม่ต้องการ IPv6 หรือต้องการผูกที่อื่น

วิธีแก้ปัญหาจาก Saif Bechan ใช้server_nameในกรณีของฉันคือ localhost แต่ไม่สามารถเข้าถึงได้ผ่านเครือข่าย

การแก้ปัญหาจาก Michael Neale นั้นดี แต่จากหลุมบ่อมีทางออกที่ดีกว่าด้วยการเปลี่ยนเส้นทาง 301;)


ดีที่คุณพยายามอ้างถึง แต่ 301 ไม่ทำงานกับ HTTPS
กรณี

5
อะไรไม่ทำงาน ส่วนเซิร์ฟเวอร์ที่ระบุนั้นใช้สำหรับ http ที่ไม่มีการเข้ารหัส (โดยไม่มี s) ทราฟฟิกจะถูกเปลี่ยนเส้นทางไปยังเซิร์ฟเวอร์ที่เข้ารหัสอย่างถาวร (ส่วนที่ฟัง 443 (https) ไม่ได้อยู่ในรายการ)
kmindi

ฉันตรวจสอบว่าใช้งานได้ดีกับ https และทุกอย่าง - @kmindi ฉันอัปเดตคำตอบของฉันโดยอ้างอิงกับคุณ - เพราะฉันคิดว่ามันเป็นวิธีที่ถูกต้อง ทำได้ดีมาก
Michael Neale

เมื่อใช้การร้องขอโดเมน (ที่ไม่ใช่ IP) จะไม่ทำงานเว้นแต่ฉันจะเปลี่ยน '[::]: 80' เป็น '80'
Joseph Lust

ที่อาจจะมีความคาดหวังพฤติกรรม: trac.nginx.org/nginx/ticket/345 ฉันอัพเดตคำตอบเพื่ออธิบายตัวเลือกการฟัง
kmindi

20

ภายในบล็อกเซิร์ฟเวอร์คุณยังสามารถทำสิ่งต่อไปนี้:

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

2
การกำหนดค่านี้ทำให้เซิร์ฟเวอร์ของฉันสร้างวนรอบการเปลี่ยนเส้นทาง
Corkscreewe

อาจทำให้มีการเปลี่ยนเส้นทางในสถานที่อื่นหรือไม่ได้เปิดใช้งาน https ในไซต์ / แอปของคุณ
Oriol

1
ไม่มีคนอื่น ๆ ที่ดูเหมือนจะทำงานยกเว้นคนนี้ ใช้รุ่น nginx: nginx / 1.10.0 (Ubuntu)
ThatGuy343

upvoted สำหรับ https: // $ host $ uri
AMB

4
นี่คือวิธีที่จะไปหากคุณอยู่เบื้องหลัง loadbalancer!
Antwan

17

ด้านบนไม่ทำงานเมื่อมีการสร้างโดเมนย่อยใหม่ตลอดเวลา เช่น AAA.example.com BBB.example.com สำหรับโดเมนย่อยประมาณ 30 โดเมน

ในที่สุดก็มีการกำหนดค่าการทำงานกับสิ่งต่อไปนี้:

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
# ...
}

ขอขอบคุณ! nginx อาจส่งคืน301 https://*/หรือยกเลิกคำขอก่อนกำหนดในคำตอบอื่นที่นี่ server_name _;ด้วย$hostเป็นคำตอบที่ทำเคล็ดลับ +1
zamnuts

1
อันนี้ดีที่สุด! อย่างไรก็ตามฉันขอแนะนำให้บางคนแทนที่_ด้วยโดเมนจริงเช่น.domain.comฉันมีเซิร์ฟเวอร์สองเครื่องและ nginx ตั้งใจนำเซิร์ฟเวอร์ตัวใดตัวหนึ่งไปยังเซิร์ฟเวอร์เริ่มต้นโดยไม่ตั้งใจ
zzz

1
นี่เป็นคำตอบเดียวที่ใช้ได้สำหรับฉันขอบคุณ!
Snowman

ขอบคุณมากเพื่อน .. ฉันได้ลองวิธีแก้ปัญหาหลายอย่าง แต่ไม่ได้ผล โซลูชั่นนี้ยอดเยี่ยมมากและใช้งานได้สำหรับฉัน ชื่อเซิร์ฟเวอร์ _; สิ่งนี้มีความหมายสำหรับ .. ฉันไม่เข้าใจ โปรดอธิบายฉันเรื่องนี้
Pavan Kumar

6

ฉันโพสต์ความคิดเห็นเกี่ยวกับคำตอบที่ถูกต้องเมื่อนานมาแล้วพร้อมกับการแก้ไขที่สำคัญมาก แต่ฉันรู้สึกว่าจำเป็นต้องเน้นการแก้ไขนี้ในคำตอบของตัวเอง ไม่มีคำตอบก่อนหน้าใดที่ปลอดภัยสำหรับใช้เมื่อใดก็ตามที่คุณมีการตั้งค่า HTTP ที่ไม่ปลอดภัยและคาดหวังว่าเนื้อหาของผู้ใช้มีรูปแบบโฮสต์ API หรือกำหนดค่าเว็บไซต์เครื่องมือแอปพลิเคชันหรือยูทิลิตี้ใด ๆ

ปัญหาเกิดขึ้นเมื่อมีการPOSTร้องขอไปยังเซิร์ฟเวอร์ของคุณ หากเซิร์ฟเวอร์ตอบสนองด้วยการ30xเปลี่ยนเส้นทางธรรมดาเนื้อหา POST จะหายไป สิ่งที่เกิดขึ้นคือการที่เบราว์เซอร์ / ลูกค้าจะอัพเกรดการร้องขอไปยัง SSL แต่ปรับลดPOSTกับGETคำขอ POSTพารามิเตอร์จะหายไปและขอไม่ถูกต้องจะต้องทำยังเซิร์ฟเวอร์ของคุณ

การแก้ปัญหานั้นง่าย คุณต้องใช้การHTTP 1.1 307เปลี่ยนเส้นทาง นี่คือรายละเอียดใน 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).

โซลูชันที่ดัดแปลงจากโซลูชันที่ยอมรับจะใช้307ในรหัสการเปลี่ยนเส้นทางของคุณ:

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

ฉันพยายามที่จะทำเช่นนี้:

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

https://stackoverflow.com/a/36777526/6076984


อัปเดตเวอร์ชันแล้วโดยไม่มี IF: paste.debian.net/plain/899679
stamster

4

ฉันกำลังเรียกใช้ ngnix อยู่เบื้องหลัง AWS ELB ELB กำลังพูดคุยกับ ngnix ผ่าน http เนื่องจาก ELB ไม่มีวิธีส่งการเปลี่ยนเส้นทางไปยังไคลเอนต์ฉันจึงตรวจสอบส่วนหัว X-Forwarded-Proto และเปลี่ยนเส้นทาง:

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

1

หากคุณreturn 301 https://$host$request_uri;เป็นการตอบสนองเริ่มต้นที่พอร์ต 80 เซิร์ฟเวอร์ของคุณอาจช้าหรือเร็วกว่านั้นในรายชื่อผู้รับมอบฉันทะที่เปิดอยู่ [1] และเริ่มถูกทารุณกรรมเพื่อส่งทราฟฟิกที่อื่นบนอินเทอร์เน็ต หากบันทึกของคุณเต็มไปด้วยข้อความเช่นนี้คุณจะรู้ว่ามันเกิดขึ้นกับคุณ:

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)"

ปัญหาคือว่า$hostจะสะท้อนกลับสิ่งที่เบราว์เซอร์ส่งในHostส่วนหัวหรือแม้กระทั่งชื่อโฮสต์จากบรรทัดเปิดของ HTTP เช่นนี้:

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

เพราะปัญหาที่บางคำตอบอื่น ๆ ที่นี่ขอแนะนำให้ใช้แทน$server_name ประเมินสิ่งที่คุณใส่ในการประกาศเสมอ แต่ถ้าคุณมีโดเมนย่อยหลายโดเมนที่นั่นหรือใช้ไวด์การ์ดนั้นจะไม่สามารถใช้งานได้เพราะใช้เฉพาะรายการแรกหลังจากการประกาศเท่านั้นและที่สำคัญกว่านั้นจะสะท้อนกลับเป็นไวด์การ์ด (ไม่ขยาย)$host$server_nameserver_name$server_nameserver_name

ดังนั้นวิธีการรองรับหลายโดเมนในขณะที่รักษาความปลอดภัย? ในระบบของฉันฉันได้จัดการกับภาวะที่กลืนไม่เข้าคายไม่ออกนี้โดยรายการแรกdefault_serverบล็อกที่ไม่ได้ใช้$hostแล้วรายการบล็อกตัวแทนที่ไม่:

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

(คุณสามารถแสดงมากกว่าหนึ่งโดเมนในบล็อกที่สอง)

ด้วยชุดค่าผสมนั้นโดเมนที่ไม่ตรงกันจะได้รับการเปลี่ยนเส้นทางที่ไหนสักแห่ง hardcoded (เสมอexample.com) และโดเมนที่ตรงกับคุณจะไปถูกที่แล้ว เซิร์ฟเวอร์ของคุณจะไม่เป็นประโยชน์ในฐานะที่เป็นพร็อกซีเปิดดังนั้นคุณจะไม่ได้รับปัญหา

หากคุณรู้สึกไม่ดีฉันคิดว่าคุณสามารถทำให้default_serverบล็อกนั้นไม่ตรงกับโดเมนที่ถูกต้องตามกฎหมายของคุณและให้บริการสิ่งที่น่ารังเกียจ . . .

[1] เทคนิค "พร็อกซี" เป็นคำที่ผิดเพราะเซิร์ฟเวอร์ของคุณไม่ออกไปข้างนอกและตอบสนองคำขอของลูกค้าเพียงส่งการเปลี่ยนเส้นทาง แต่ฉันไม่แน่ใจว่าคำที่ถูกต้องคืออะไร ฉันยังไม่แน่ใจว่าเป้าหมายคืออะไร แต่จะเติมบันทึกของคุณด้วยเสียงรบกวนและใช้ CPU และแบนด์วิดท์ของคุณดังนั้นคุณอาจหยุดการใช้งาน


0

ดูเหมือนว่าไม่มีใครได้รับจริง 100% จะมีพอร์ต 80 คำขอไปที่ 443 เทียบเท่าของพวกเขาสำหรับเว็บเซิร์ฟเวอร์ทั้งหมดที่คุณจำเป็นต้องใช้ฟังคำสั่งไม่สั่ง server_name เพื่อระบุจับทั้งหมดชื่อ ดูเพิ่มเติมที่https://nginx.org/en/docs/http/request_processing.html

เซิร์ฟเวอร์ {
    ฟังค่าเริ่มต้น 80
    ฟัง [::]: 80 ค่าเริ่มต้น;
      ส่งคืน 307 https: // $ host $ request_uri;
}
  • $ host รับชื่อโดเมนย่อย
  • 307 และ 308 รวมทั้ง POST และ GET คำขอ URI
  • 307 เป็นแบบชั่วคราวเปลี่ยนเป็นแบบถาวร 308 หลังจากการทดสอบอย่างละเอียด:

และตรวจสอบให้แน่ใจว่าคุณตรวจสอบสิ่งที่มีอยู่แล้วใน /etc/nginx/conf.d/ เพราะบ่อยกว่าที่ฉันมีปัญหาที่ default.conf ส่งคืน vhost ที่มีอยู่แล้ว คำสั่งของฉันในการทำงานกับปัญหา nginx เริ่มต้นเสมอโดยการย้ายไฟล์เริ่มต้นออกวางความคิดเห็นแบบทีละบรรทัดเพื่อดูว่ามันผิดพลาดที่ไหน


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.