# 用Nginx架設local端https應用 ###### tags: `Nginx` `Ubuntu` `WSL` `WebApp` [TOC] ## 背景 某天閒來無事的我寫了一個Web App,想要分享給別人使用,剛好實驗室有固定IP的電腦可以用,為了省錢(X)就決定直接開在上面。一開始一切都很美好,用Docker開好了MySQL server,Node.js的server也順利開起來連上DB,localhost測起來相安無事,於是乎,拿起我的小筆電,連連看實驗室IP,恩,可以看到網頁,但...好像有某個功能壞了(?) {%hackmd 2q2OKXhpSca0en6d0b1GEg %} 原本在WebApp中有個功能是可以直接複製文字到剪貼簿中,用到了[Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard),在localhost中測試時一切都好好的,也可以順利地要求剪貼簿權限,同意後就有相關API可以call,但這時卻連權限要求的通知都沒有跳出來,從網址列點進去權限面板看看發現,不對啊!怎麼剪貼簿權限連開都沒辦法開,正常來講應該要像下面這張圖片一樣,Clipboard那邊是可以選擇要不要給權限的  <center class="description"> <small> 在Edge上面看到的關於網頁各種權限的管理>在Edge上面看到的關於網頁各種權限的管理 </small> </center> <br/> 但我點進去卻發現那個selector完全是disabled的,不給按QQ仔細看了敘述之後才發現,應該是因為網站是用http所以才會被擋掉權限,但這也太狠了,連我想要把自記置身於危險之中都不讓,不過其實早就有傳言Chrome會加強安全性,之後連http的網站都沒辦法訪問,用Chromium核心的Edge自然也是跟上這股歪風(X)  <center class="description"> <small> 不給按 </small> </center> <br/> 雖然只是個不起眼的小功能,但不能用了還是會感到很阿雜,手動選取文字再按 `ctrl+c` 就是很麻煩(懶),所以決定要幫這個小專案加上https。 ## 實作 當然這個只是個個人用的小project,不太可能為了它去買個網域+SSL憑證,所以想說可以自己sign一個憑證來用用,雖然瀏覽器會跳警告說這是不安全的憑證,但至少還算是有個https可以用,於是我們就開做吧! ### Web App 這個部分就沒甚麼好講的了,基本上任何的APP都可以用這個方法賦予它https,這次我是用nest-next開了一個前+後端的APP在windows上的port 8081上面,連線到localhost:8081確定APP可以正常運行,這時候如果從外部連到http://xxx.xxx.xxx.xxx:8081是可以順利看到運行中的APP的,但如果切https://xxx.xxx.xxx.xxx:8081則完全沒有畫面 ### 建立SSL(Secure Sockets Layer)憑證 這部分我其實沒有很熟悉,主要是參考[這篇](https://juejin.cn/post/7081627429646630942)一步一步做的,而SSL的原理可以看[這裡](https://support.unethost.com/index.php?rp=/knowledgebase/82/SSLSSL-certificate.html),簡言之SSL會加密browser與server間傳輸的通訊內容,使傳遞的資料在傳輸過程中,不會被第三方(如proxy、router、攔截)看見明文,為了達到這個目的,server會生成一對可以相互加解密的公鑰(public key)與私鑰(private key),並將public key工愾讓所有連線的browser都可以取得,在傳輸資料前,瀏覽器會以這支公鑰加密內容,server收到data後,再以自己保有的私鑰解密。 但瀏覽器怎麼知道這隻公鑰是不是真的從server發出來的呢?如果有惡意人士替換掉了傳給browser的公鑰,那麼他就有辦法從中攔截回傳的data並解密出明文。所以現行server發出來的公鑰,必須經過權威第三方,也就是CA(Certificate Authority)去簽發認證你是這個公鑰的擁有者,這個簽發過的公鑰就被稱為憑證(certificate)。  <center class="description"> <small> 憑證生成流程圖 </small> </center> <br/> 為了方便管理等一下生成的檔案,我在`/home/{username}`新建一個名為`CA`的資料夾,以下的指令都是在該資料夾中執行。第一步我們先生成一個rsa非對稱加密演算法的2048位元長的密鑰,這隻密鑰等一下會同時作為上圖中的private key(Application)與private key(CA)的用途 ```bash openssl genrsa -out privatekey.pem 2048 ``` 接者以這支密鑰建立憑證簽章請求檔案,這邊會有一輪身家調查,但我們也不是正經(?)的公司,所以就意思意思回答一下即可 ```bash openssl req -new -key privatekey.pem -out private-csr.pem ``` 最後再以同一隻密鑰來簽發憑證,這裡面的參數`-days`可以調久一點,就不會需要常常回來更新憑證 ```bash openssl x509 -req -days 365 -in private-csr.pem -signkey privatekey.pem -out certificate.pem ``` 簡單的三個步驟我們久有了一組~~免費~~自製的SSL憑證了,接下來就要把這些憑證掛到Nginx的server上面。 ### 安裝Nginx :::success Nginx是非同步框架的網頁伺服器,也可以用作反向代理、負載平衡器和HTTP快取。該軟體由俄羅斯程式設計師伊戈爾·賽索耶夫開發並於2004年首次公開發布。2011年成立同名公司以提供支援服務。2019年3月11日,Nginx公司被F5網路公司以6.7億美元收購。 <span style="float:right">------ 維基百科</span> ::: 所以我們的目標就是開啟Nginx的反向代理,把80 port跟443 port的流量都導向8081 port,然後再掛一個SSL憑證來啟用https,說起來簡單做起來難,好在網路上都已經有各路大神給出的詳細指示,照著做準沒錯(吧 但我從來沒有去過Nginx的官網,之前都是直接下指令在Linux上安裝的,那要怎麼在Windows上面裝呢?很簡單,不要裝在Windows上面XDD這時候我們的好朋友WSL(Windows Subsystem for Linux)就派上用場啦!在Terminal裡面打開WSL,輸入以下指令一鍵安裝 ```bash sudo apt-install nginx ``` 等它安裝好了之後,再輸入以下指令檢查,應該就會顯示版號了 ```bash nginx -v nginx version: nginx/1.18.0 (Ubuntu) ``` 接者要在Ubuntu上面把Nginx打開 ```bash sudo service nginx start * Starting nginx nginx ``` 他預設會在80 port上面開啟自帶的網頁,這時候到localhost去看就可以看到Nginx自帶的首頁 ### Nginx反向代理(Reverse proxy) 正向的代理(proxy)是指我們在瀏覽網頁的時候,不直接用我們的電腦去跟server溝通,而是將請求發到proxy server,再由後者去向server request資料,好處是client端的IP位址可以被隱藏起來,server只會知道proxy server的IP,另外就是可以用proxy server來做一些cache,讓瀏覽時的反應速度更好。 而反向代理則是網路上的用戶在瀏覽我們的時候,不能直接跟server request資料,而是統一向reverse proxy server要求,後者根據要求的資料來將流量導至背後不同的server,好處是我們可以在同一台電腦上開多個server在不同的port,並由同一個proxy server對外接口,根據user request的網址來決定要將流量導至哪一個server,另外也可以做到load balance的功能,所謂load balance就是同樣的server我有好幾台,在request叫到proxy server的時候,去分流到不同的機器上面,降低每一台機器再同意時間的負擔,從而提供比較流暢的體驗。  <center class="description"> <small> Reverse proxy示意圖 <a href="https://www.asustor.com/admv2?type=2&subject=8&sub=153&lan=en" target="_blank">source</a> </small> </center> <br/> 要在Nginx中做反向代理也不難,只要修改相關config檔就可以了,這邊我直接修改default的config,首先開啟設定檔,我這邊是直接用vim修改,但其實升級WSL2之後可以支援WSLg,就可以安裝一些GUI的編輯器來開啟Linux上的檔案 ```bash sudo vim /etc/nginx/sites-available/default ``` :::warning /etc/nginx/sites-available/default預設是readonly的檔案,若沒有修改權限不加sudo就直接編輯,最後會無法寫入檔案 ::: 設定檔打開後大概會長得像是這樣 ```nginx server { listen 80; listen [::]:80 ; # SSL configuration listen 443 ssl ; listen [::]:443 ssl ; server_name www.example.com; root /var/www/nginx-default/; index index.html location / { # [...] } location /foo { # [...] } location /bar { root /some/other/place; # [...] } } ``` 每一個block會用大括號括起來,block裡面的參數則是每一行以`key value;`的形式去設定,如果要下comment就用`#`放在一行的最開始。簡單介紹一下這裡面出現的一些參數: - `server` 是一個基礎的block,nginx會依照block裡面的設定去開一台server - `listen` 則是設定這台server要監聽那些port,第二列的`[::]:80`是設定IPv6的port - `server_name` 是設定這台sever的網域名稱,這裡如果沒有網域可以設定為`_`代表任何網域 - `root` 是設定這台server的根目錄,如果有要serve靜態檔案的話會從這裡面找 - `index` 是設定沒有path name的時候要回傳的首頁檔案 - `location` 可以個別設定server在收到不同路徑的request時要套用的設定 接著來設定proxy,這邊想做的是將連進來的請求,全部導向我們的Web App,上面已經設定好監聽80與443 port了,剩下要做的就是重新導向,將`location /`裡面的內容清空,設定為以下這樣 ```nginx location / { proxy_pass http://172.27.224.1; proxy_redirect off; proxy_set_header Host $proxy_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } ``` 其中 - `proxy_pass` 是用來指定proxy的目的地,就是我們App所在的位置,這邊因為App是開在windows上所以IP位置是填WSL所看到的windows網卡接口 - `proxy_redirect` 是用來改寫server回傳的301或302(redirect)中header的字串,這邊目前還用不到,所以先設置為off - `proxy_set_header` 則是加一些額外的header到請求的封包裡面 :::info 要如何知道windows主機在WSL上面的ip位置?可以在windows下輸入指令`ipconfig`找到裡面`Ethernet adapter vEthernet (WSL)`底下的ip就可以了 ::: 記得要將其他的location block給刪掉或是註解起來,才不會有些路徑被導向別的地方。config檔存好了之後,要重啟nginx才能套用變更,輸入以下指令 ```bash sudo service nginx restart ``` 這時候如果連線到http://localhost,應該會發現沒有辦法順利看到我們的APP,為甚麼呢?雖然我們已經順利把流量導回windows了,但是在windows中,WSL所在的區域網路(172.27.224.1),跟localhost(127.0.0.1)所在的區域網路是不同的,所以沒辦法直接互通,我們需要將這個流量再forword過去。 ### Port forwording 前面提到,不同的區域網路是不能互通的,好在windows有內建portproxy的指令,讓我們可以把流量導過去,首先用**管理員權限**開啟terminal,輸入以下指令 ```ps netsh interface portproxy add v4tov4 listenport=80 listenaddress=172.27.224.1 connectport=8081 connectaddress=127.0.0.1 ``` 整串指令的意思是:「我們要修改網卡介面的設定,增加一個port proxy,從IPv4到IPv4,將所有進來172.27.224.1 port 80的流量都導向127.0.0.1的port 8081」 可以輸入以下指令確認是否有添加成功 ```ps netsh interface portproxy show all ``` 這樣就可以從http://localhost不加port,看到我們的Web App了,但這時候還沒有設定SSL的憑證,所以https是沒辦法連線的 :::info 如果要刪除這個portproxy可以用以下指令 ```bash netsh interface portproxy delete v4tov4 listenport=80 listenaddress=172.27.224.1 ``` ::: :::warning 記得將上面指令中的`172.27.224.1`改為你電腦上的WSL端口IP ::: ### 設定SSL憑證 現在我們nginx server也架好了,SSL憑證也簽完了,萬事俱備只欠東風,最後就只要把這兩個東西串在一起就大功告成,而串在一起也非常的簡單,就只需要在剛剛的config檔裡面,添加兩行設定,讓nginx server知道我們憑證與私鑰的檔案所在 ```nginx server { # ... ssl_certificate /home/{username}/CA/certificate.pem; ssl_certificate_key /home/{username}/CA/privatekey.pem; } ``` 存檔後,一樣再執行一次重啟nginx的指令,就可以連線到https://localhost了,但由於我們的憑證是自己簽署的,所以瀏覽器會把它檔下來,但只要在進階選項中,點繼續前往就可以了  <center class="description"> <small> 被擋下來了 </small> </center> <br/> ## 疑難雜症 當然,事情不會一帆風順,一定會有什麼小小的問題藏在細節裡面。當上面一切都設定好了之後,也測試了http跟https都可以看到我們的APP,但...localhost可以連到,為什麼改用固定IP就連不上了呢?!! ### localhost可以連線,IP無法連線 這個問題其實跟前面的[Port Forwording](#Port-forwording)類似,從IP位址連進來的流量跟localhost是在不同的網路介面上,所以無法互通,解法就是如同前述指令,但將WSL的IP改為固定IP,監聽的port要有80(http)與443(https),都導向127.0.0.1的80與443,就可以用IP連線了 ```ps $IP={固定IP} netsh interface portproxy add v4tov4 listenport=80 listenaddress=$IP connectport=80 connectaddress= 127.0.0.1 netsh interface portproxy add v4tov4 listenport=443 listenaddress=$IP connectport=443 connectaddress= 127.0.0.1 ``` ### 用其他電腦沒辦法連進來固定IP 明明ping也ping的到,但為甚麼從瀏覽器連進來的時候卻是一片虛無呢?原因大概是 >防火牆沒開 解法也不難,打開防火牆設定(可以用windows內建的搜尋功能),點左上角的`Inbounce Rules`(不知道中文要翻成啥XD)  再點右上的新增規則  Type選擇Port  開啟TCP的port 80與443  允許所有連線  套用在所有類型的網路  設定一個名稱  最後按確定就可以從外面連線進來啦 ## 結語 如果一路從上面看下來可能會發先有一個地方怪怪的,我們要大費周章地把WSL裡面的流量forword回windows,可是開在WSL上的nginx server卻一啟用就可以從localhost連上,這其實是WSL在設計的時候,windows是可以直接訪問到WSL內的localhost的,詳細的情況我沒有很清楚,但windows上的localhost與WSL內的是同一個,但如果要從WSL連回windows,就必須要用interface的IP去連,所以我們將IP進來的流量導向windows上的localhost,回直接被WSL中的nginx server抓住,但nginx在做proxy的時候,沒辦法指回windows中的localhost,才會需要先導向172.27.224.1在從windows中forword到127.0.0.1。 這麼看來一開始好像應該直接在windows上裝nginx就不會需要這麼麻煩的轉來轉去了,但也是因為這一番操作,對WSL跟一些linux上的指令更熟悉了一點,好像也不是壞事(?)。 其實為了寫這一篇出來,雖然整個網站都可以用了,但還是去查了很多SSL或nginx的細節,比起只是讓它可以work,仔細了解背後的原理,我覺得更能夠加深記憶,才不會每次都是複製貼上了事XDD 儘管如此,Nginx還有許多這次沒用到所以不熟悉的部分,像是load balance,在AWS上只要點幾個鍵就可以設定好的東東,到底背後的細節是甚麼?這要等之後有機會再來研究了(大概率不會
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.