ลบ“ www” และเปลี่ยนเส้นทางไปที่“ https” ด้วย nginx


57

ฉันต้องการสร้างกฎใน nginx ที่ทำสองสิ่ง:

  1. ลบ "www." จาก URI คำขอ
  2. เปลี่ยนเส้นทางไปที่ "https" หาก URI คำขอคือ "http"

มีตัวอย่างมากมายเกี่ยวกับวิธีการทำสิ่งเหล่านั้นทีละอย่าง แต่ฉันไม่สามารถหาวิธีแก้ปัญหาที่ทำได้อย่างถูกต้อง (เช่นไม่สร้างลูปการเปลี่ยนเส้นทางและจัดการทุกกรณีอย่างเหมาะสม)

จำเป็นต้องจัดการกับกรณีเหล่านี้ทั้งหมด:

1. http://www.example.com/path
2. https://www.example.com/path
3. http://example.com/path
4. https://example.com/path

สิ่งเหล่านี้ควรจบที่https://example.com/path (# 4) โดยไม่ต้องวนซ้ำ ความคิดใด ๆ


ฉันเพิ่งเปลี่ยนเส้นทาง www.mydomain.com ไปยัง mydomain.com ที่ระดับ DNS และเพิ่ม 301 สำหรับที่ไม่ใช่ https ไปยัง https ใน nginx ดูเหมือนว่าควรจะดี¯ \ _ (ツ) _ / ¯
jonathanbell

คำตอบ:


94

วิธีที่ดีที่สุดในการทำสิ่งนี้ให้สำเร็จคือการใช้เซิร์ฟเวอร์บล็อกสามอัน: หนึ่งบล็อกสำหรับเปลี่ยนเส้นทาง http ไปยัง https, หนึ่งบล็อกเพื่อเปลี่ยนเส้นทาง www www-name เป็น no-www และอีกหนึ่งบล็อกเพื่อจัดการคำขอจริง เหตุผลในการใช้บล็อกเซิร์ฟเวอร์เพิ่มเติมแทน ifs คือการเลือกเซิร์ฟเวอร์จะดำเนินการโดยใช้ตารางแฮชและรวดเร็วมาก การใช้ระดับเซิร์ฟเวอร์หากหมายถึงว่าหากมีการเรียกใช้สำหรับทุกคำขอซึ่งสิ้นเปลือง นอกจากนี้การจับ uri ที่ร้องขอในการเขียนซ้ำนั้นสิ้นเปลืองเนื่องจาก nginx มีข้อมูลนี้อยู่แล้วในตัวแปร $ uri และ $ request_uri (โดยไม่ต้องมีสตริงการสืบค้นตามลำดับ)

server {
    server_name www.example.com example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    ssl_certificate /path/to/server.cert;
    ssl_certificate_key /path/to/server.key;
    server_name www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    ssl_certificate /path/to/server.cert;
    ssl_certificate_key /path/to/server.key;
    server_name example.com;

    <locations for processing requests>
}

2
บล็อกกลางจำเป็นหรือไม่ บล็อกแรกไม่ได้เขียนใหม่จาก www เป็น non-www แล้วใช่ไหม
pbreitenbach

3
บล็อกแรกจัดการ http เท่านั้น บล็อกกลางจำเป็นต้องเปลี่ยนเส้นทางคำขอ https จาก https: // www.example.com/ ไปยัง https: // example.com/ (ขออภัยสำหรับพื้นที่พิเศษผมไม่สามารถทำให้มันแสดงให้เห็นเป็นอย่างอื่น https)
kolbyjack

1
เพียงบันทึกย่อการจัดรูปแบบเล็กน้อย - หากคุณต้องการหลีกเลี่ยงการสร้างลิงก์คุณสามารถใส่ข้อความแสดงความคิดเห็นไว้ในเครื่องหมายคำพูดหลัง `อันที่อยู่ใต้เครื่องหมายตัวหนอน มันจะปรากฏขึ้นเช่น:https://example.com/
ไซคลอปส์

9
บล็อกที่สองยังต้องการข้อมูลใบรับรองด้วย
ricka

3
ลองคำตอบนี้ฉันพบปัญหาอื่น คิดว่าฉันสามารถเปลี่ยนเส้นทาง 301 จากwww.sub.example.comไปยังsub.example.comแล้วรับใบรับรอง SSL สำหรับsub.example.comตอนนี้ฉันรู้ว่าการตรวจสอบใบรับรอง ssl เกิดขึ้นก่อนการเปลี่ยนเส้นทาง 301 ดังนั้นจึงไม่สามารถทำงานได้ คำอธิบายเพิ่มเติมได้ที่นี่: serverfault.com/a/358625/144811
Gruzzles

11

สิ่งนี้ใช้ได้กับฉัน:

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

server {
    listen              443 ssl;
    server_name         www.yourdomain.com;
    ssl_certificate     /path/to/certificate.crt;
    ssl_certificate_key /path/to/private/key.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    return              301 https://yourdomain.com$request_uri;
}

server {
    listen              443 ssl;
    server_name         yourdomain.com;
    ssl_certificate     /path/to/certificate.crt;
    ssl_certificate_key /path/to/private/key.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;

    # do the proper handling of the request
}

โปรดทราบว่าทั้งสอง yourdomain.com และ www.yourdomain.com ต้องอยู่ในใบรับรอง SSL ของคุณ นี้เป็นไปได้ที่มีใบรับรองตัวแทนหรือกับเซิร์ฟเวอร์สำรองชื่อตามที่อธิบายไว้ที่นี่ ตรวจสอบhttps://www.startssl.comเพื่อรับใบรับรองที่ดีและฟรี ( Edith : เริ่มต้นด้วย Chrome รุ่น 56 ใบรับรอง startssl จะไม่น่าเชื่อถืออีกต่อไปลองhttps://letsencrypt.org/แทน)


อันนี้ใช้งานได้จริง แต่ฉันคิดว่ามันสามารถทำได้อย่างชัดเจนมากขึ้นโดยไม่ต้องมีสายการตั้งค่าที่ซ้ำกันจำนวนมาก
zloynemec

@zloynemec คุณสามารถใส่ข้อมูล SSL ลงในไฟล์. conf แยกต่างหากและใช้includeกฎเพื่อเพิ่มลงในบล็อกเซิร์ฟเวอร์ทั้ง SSL
Igettäjä

นอกจากนี้หากคุณกำลังใช้ cloudflare คุณจะต้องจ่ายใบรับรอง $ 10 / mo เพื่อให้สามารถเปลี่ยนเส้นทางและพร็อกซีโดเมนย่อย 2 โดเมน (www + บางสิ่งบางอย่าง) แจ้งให้เราทราบหากมีวิธีแก้ปัญหา
Freedo

7

หลังจากใช้เวลามากกับคดีที่คล้ายกันหลายร้อยรายการฉันได้พบตัวอย่างต่อไปนี้ มันสั้นและสามารถปรับแต่งให้เข้ากับอะไรก็ได้

server {
    listen 80;
    listen 443 ssl;
    server_name example.com www.example.com;
    ssl_certificate /path/to/my/certs/example.com/fullchain.pem;
    ssl_certificate_key /path/to/my/certs/example.com/privkey.pem;

    # Redirect to the correct place, if needed
    set $https_redirect 0;
    if ($server_port = 80) { set $https_redirect 1; }
    if ($host ~ '^www\.') { set $https_redirect 1; }
    if ($https_redirect = 1) {
        return 301 https://example.com$request_uri;
    }

    location / {
    # ...
}

โอ้ แต่ifมันชั่วร้าย !

ใช่มันสามารถเป็น แต่มีอยู่ด้วยเหตุผลและไม่ควรทำอันตรายกับผู้ที่รู้วิธีการใช้อย่างถูกต้อง ;)


ฉันชอบสิ่งนี้ แต่คุณมีข้อมูลใด ๆ เกี่ยวกับประสิทธิภาพหรือไม่ ขอขอบคุณ!
Freedo

1
สุจริตฉันไม่เคยเปรียบเทียบว่า แต่ฉันเชื่อว่าจะมีผลกระทบแทบจะไม่เมื่อเทียบกับกฎแยกต่างหากเนื่องจากผลที่ออกมาค่อนข้างเหมือนกัน
emyller

เกณฑ์มาตรฐานในการเปลี่ยนเส้นทาง? มันไม่จริงใช่ไหม? (คำถามจริงไม่ใช่โทรลล์ ^^)
เมทริกซ์

3

ฉันต้องการกลับมาพร้อมรหัสตอบกลับเพื่อให้เบราว์เซอร์รู้ว่าคุณกำลังเปลี่ยนเส้นทางไปยัง URL อื่น

server {
    listen   80;
    server_name  www.example.com;

    return 301 https://example.com$request_uri;
}

ดังนั้นการกำหนดค่าเซิร์ฟเวอร์อื่นบล็อกสำหรับ https

server {
        listen   443 ssl;
        server_name  example.com;
        ...
    }

0

วิธีสร้างบล็อกเซิร์ฟเวอร์สำหรับวัตถุประสงค์นี้:

server{
    listen 80;
    server_name www.example.net example.net;
    rewrite ^(.*) https://example.net$1 permanent;
}

จากนั้นรีสตาร์ท nginx


ฉันได้รับข้อผิดพลาด "ชื่อเซิร์ฟเวอร์ที่ขัดแย้งกัน" เมื่อรีสตาร์ท นอกจากนี้คำสั่งนั้นจะไม่ฟังบนพอร์ต 443 สำหรับ SSL และฉันต้องกังวลเกี่ยวกับการเปลี่ยนเส้นทางhttps://www.example.comไปhttps://example.comเช่นกัน
Devin

0

ฉันคิดว่ามันน่าจะใช้ได้นะ

ในการกำหนดเซิร์ฟเวอร์ HTTP ธรรมดาของคุณสิ่งที่แนะนำเช่น anthonysomerset คือ:

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

จากนั้นนิยามเซิร์ฟเวอร์ SSL ของคุณ:

if ($host ~ /^www\./) {
  rewrite ^(.*) https://example.net$1 permanent;
}

วิธีนี้การเปลี่ยนเส้นทางจะเกิดขึ้นเพียงครั้งเดียวต่อคำขอไม่ว่าผู้ใช้จะไปที่ URL ใด


ขอบคุณมาก ฉันต้องเปลี่ยนเงื่อนไขของคุณเป็นif ($host = 'www.example.com') {เพราะ regex ของคุณไม่ทำงานสำหรับฉันแม้ว่า ไม่รู้ว่าทำไมมันดูถูกต้อง
Devin

โปรดทราบว่าหากเป็นสิ่งที่ชั่วร้ายและโดยทั่วไปจะใช้วิธีเปิดเผยได้ดีกว่า
Blaise

0

นี่คือตัวอย่างเต็มรูปแบบที่จบลงด้วยการทำงานสำหรับฉัน ปัญหาคือว่าฉันไม่มีรายละเอียด ssl ( ssl_certificateฯลฯ ) ในบล็อกการเปลี่ยนเส้นทาง www อย่าลืมตรวจสอบบันทึกของคุณ ( sudo tail -f /var/log/nginx/error.log)!

# HTTP — redirect all traffic to HTTPS
server {
    listen 80;
    listen [::]:80 default_server ipv6only=on;
    return 301 https://$host$request_uri;
}

# HTTPS — redirects www to non-www
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;

    # Use the Let's Encrypt certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Include the SSL configuration from cipherli.st
    include snippets/ssl-params.conf;
    return 301 https://example.com$request_uri;
}

# HTTPS — proxy all requests to the app (port 3001)
server {
    # Enable HTTP/2
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com sub.example.com;

    # Use the Let's Encrypt certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Include the SSL configuration from cipherli.st
    include snippets/ssl-params.conf;

    # For LetsEncrypt:
    location ~ /.well-known {
        root /var/www/html;
        allow all;
    }

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-NginX-Proxy true;
        proxy_pass http://localhost:3001;
        proxy_ssl_session_reuse off;
        proxy_set_header Host $http_host;
        proxy_cache_bypass $http_upgrade;
        proxy_redirect off;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.