軟體: SSL, Let’s Encrypt, certbot, and NginX.
眼尖的讀者可能已經發現到了,本站的網址左邊原本是三角形或者寫著"Not secure",但是現在變成了一個小巧可愛的鎖頭,如果把網址複製出來,就可以看到原先的"http://“變成了"https://,這代表本站開始使用安全連接層Secure Socket Layer (SSL),如果有駭客攔截了網站伺服器到你家電腦中間的網路封包,他也只能看到加密的資料,沒有我們兩端的授權金鑰,他就解不出雜亂的加密資料到底是什麼內容。事實上2021大部分的網站已經都支援SSL安全連線了,如果你訪問了"http://“開頭的網頁,你的伺服器可能還會擋著你問你想清楚要繼續瀏覽了嗎?所以啦,順應這個時代的潮流,筆者就也跟著搞個小鎖頭來啦。
先說在前頭,SSL網域是能直接花錢購買的,網路上很多網域提供商提供各種方案,一整年的租金也就五美元到20美元之間,依據加密總類、效能、附加服務,還有客製化的程度來收不同的價錢。但是,本次企劃的挑戰之一就是不花錢,土炮完成所有任務,看看依靠免費資源我們能走多遠。
根據幾天下來的資料蒐集,發現現在流行的SSL架站方式就是使用反向代理,而反向代理軟體有兩種蠻熱門的,第一個是NginX,另一個是後起之秀Caddy。雖然Reddit鄉民有蠻多人大推Caddy,因為它的安裝設定相對簡單,但是爬了更多的網路文章之後,很多老手給出建議,說其實NginX是因為功能強大才給人設定複雜的錯覺,如果明確知道自己的目的是什麼,NginX的設定其實沒幾行,而且NginX的社群龐大,真有疑難雜症搜不到答案,直接上各大發問網站去問,很容易就能釣出專家來指點迷津。但如果使用的是Caddy,遇到困難的時候可能也沒多少人能幫忙回答。再者,NginX已經被很多大公司拿去優化全世界超過五百萬個網站了。
吸收了一些網路教程之後,他們通常都使用NginX建立一個反向代理伺服器,卡在原本的網站伺服器與外部用戶之間,接著使用certbot這個方便的程式跟Let’s Encrypt這個認證機構取得綁定我們網站的的加密金鑰,然後在這個反向代理伺服器上用已經取得的金鑰來啟用SSL,但是我的需求稍微不一樣的地方,就在於我想把所有東西全部塞進我的樹莓派伺服器上,我只有一台實體伺服器,所有的軟體伺服器服務只透過同一台機器的不同連接埠以及區域網路在互相溝通,於是我就在不知不覺的情況之下逐漸踏進了一個坑裡。以下的安裝已經排雷填坑了,只有在一些痛點特別敘述一下,請安心服用。
首先當然是SSH連線進去樹莓派,接著輸入
$ sudo apt install certbot
$ certbot certonly --standalone -d <the no-ip address for your site>
其中的<the no-ip address for your site>不用加"http://",certbot會問一些問題,但是都按enter使用預設選項當作回答就可以,連最後放置兩個金鑰檔案的地方都採用預設路徑就可以。它會在/etc/letsencrypt/live/<the no-ip address for your site>/存放加密金鑰跟一些相關的檔案。certbot預設使用樹莓派的埠80對外連線,所以防火牆跟router都要先開好埠80才能順利取得金鑰。
有了金鑰之後要繼續打個鎖
$ sudo apt install nginx
安裝好NginX在設定它之前,我們需要先登入router做port forwarding的頁面,把原本為了HUGO server設定的WAN 80轉LAN 1313改成,WAN 80轉LAN 80,內外兩頭都設定為80。這是為了把反向代理伺服器卡原本的連線中間,接收外網從80進來內網的訪問請求。然後再加開一個WAN 443轉樹莓派位址 LAN port 443,這兩個連接埠都將被NginX攔截。與此同時,我們也需要在樹莓派上打通80跟443這兩個連接埠。這兩個關卡都打通之後,我們就可以開始設定NginX
$ sudo vi /etc/nginx/nginx.conf
將以下幾行貼在http {...}這個大括弧裡面最後的地方。
server {
listen 80;
server_name <the no-ip address for your site>;
return 301 https://<the no-ip address for your site>$request_uri;
}
server {
listen 443 ssl;
server_name <the no-ip address for your site>;
ssl_certificate /etc/letsencrypt/live/<the no-ip address for your site>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<the no-ip address for your site>/privkey.pem;
local / {
proxy_pass http://<the designated LAN ip>:1313;
}
}
這幾行程式碼分為兩個server區塊,兩種寫法的不同的原因是這兩個請求來源不同、在NginX伺服器內的處理不同、目的地的性質也不同,簡單解說如下:第一區聆聽外網傳來要透過埠80訪問的請求,然後轉成"https://“加密網址的請求。這樣的寫法一般稱之為 重新導向 。這樣一來如果使用者打的網址是"http://",他電腦上的瀏覽器也會被NginX提醒要自動改成"https://",接著NginX會送回走安全連線的內容到使用者的瀏覽器上顯示。
第二個server區塊比較大,前兩行跟上一個區塊很像,讀者應該能猜到用意,NginX在這個區塊聆聽埠443傳來要訪問我們網址的請求。 接著兩行ssl相關的程式碼,就是讓NginX知道金鑰路徑,藉此啟用SSL安全連線。江湖上都是把埠443用來傳加密請求,所以聆聽443並且在此開啟SSL安全連接。最後一個子區塊local/ {...}精簡扼要,但卻是我摔進坑裡好不容易才爬出來的血淚結晶之一。他其實很好懂,在第一個server區塊裡面我們用return 301 ...收尾,意思是收到請求之後要轉去哪裡,所以在第二個區塊也應該規定要把請求往哪裡送去,NginX的寫法就是把HUGO server的真實ip與連接埠包在 local / {...}。這個寫法稱為 反向代理 reverse proxy ,而且是反向代理中不帶任何附加內網優化的最粗淺反向代理。
小結一下,這第二個server區塊接收外網透過埠443訪問的請求,然後代理HUGO server真實位置。所謂 代理 就是仲介人,買方跟賣方都透過仲介人進行交易斡旋。只是在網路這方面再分為 正向代理 跟 反向代理 : 正向代理保護使用者,而反向代理保護伺服器。使用者跟伺服器定義了 請求 的方向性,也定義了代理伺服器的種類。言歸正傳,現在在樹莓派上,又多了這個NginX伺服器,外面傳來的訪問請求只要是給<the no-ip address for your site>,也就是server_name,NginX伺服器就知道收到的外部請求是想要訪問HUGO server。而待會從HUGO server傳回來的資料經過SSL加密之後,根據NginX預設規則記下的請求來源往回傳出外網的使用者。(小心了,NginX要經歷過外部請求,才能知道被它代理的伺服器準備好的資料要往外送去哪裡,在沒有外部請求的情形,被他代理的伺服器如果想要發主動傳資料給NginX沒見過的網址,這筆資料將會被丟包。)
還有一件事情需要注意,我這兩個區塊的寫法已經是精簡過後的寫法,NginX提供的指令、能做的優化、能導入導出的操作還有很多,為了避免離題,我們這就不要講太多了,有興趣的讀者不妨多爬點NginX或者反向代理的文章。
好極了,現在如果開啟瀏覽器,打入no-ip申請來的網址,你會發現網址列左邊出現小小的鎖頭,加密連線成功了,嗎?
點進去隨便一篇網誌,我才發現我上一篇辛苦弄好的留言區不見了。哎呀!
![]() |
|---|
| Original servers' deployment |
我們整理一下原本的連線方式,看看哪裡出了問題。如上圖所示,原先使用者使用我們提供的no-ip網址配合預設的埠80要訪問我們的Main Web server(HUGO),中間透過router轉埠把請求轉給樹莓派在內網的ip,HUGO再要求Commento server調閱PostgreSQL伺服器的資料庫,取得留言資料後Commento製作出留言區的部份網頁,Commento把這個部份網頁嵌回Hugo產生的網誌頁面最下方,傳回的網址是寫在Commento裡透過"Add Domain"註冊的no-ip網址。(這裡省略了不產生網頁的PostgreSQL伺服器,總共只有兩個伺服器在產生html網頁讓HUGO往外呈現。)
現在HUGO變成了SSL連線,但是我們從上面一路走來卡進了一個NginX反向代理伺服器,反向代理伺服器佔走了樹莓派上的埠80,導致Commento做好部份網頁之後傳給NginX,但HUGO一直沒收到留言區部份網頁,所以就沒顯示出留言區。恩,這個推敲似乎合理。
但是筆者在仔細偵錯幾個小時之後,注意到兩件事情:第一是當初在Commento註冊回給HUGO的目標位址已經使用no-ip網址註冊,即便有了反向代理,從Commento出發往HUGO的路徑被NginX截獲,但是因為請求目標是no-ip網址,符合上面兩個server區塊裡寫好的反向代理規則,應該能抵達HUGO。
第二件是如果從HUGO出發的調閱留言區請求有抵達Commento,但是來源HUGO的LAN ip位址或者連接埠有變動,Commento會拒絕調閱資料並且立刻回傳 “This domain is not registed with Commento." 並且秀在原本預計留言區會出現的位置。可是我建立反向代理之後,並沒有出現這句錯誤訊息。言下之意是HUGO傳出的請求根本沒有抵達Commento,被NginX代理的伺服器被關入了一個子領域,如果沒有特別設定NginX,矮了一階的HUGO就無法向上層訪問Commento,因為NginX沒紀錄過Commento的位址。於是請求有去無回,Commento根本沒被要求要產生留言區或者發出錯誤訊息。
你如果覺得以上詞彙比較難理解,那麼有個不錯的比喻,且聽我娓娓道來:從前從前,釉衣村有個年輕人叫做永強,好不容易父母把他拉拔長大,文才武藝也是頂呱呱,於是永強跟父母商量了一下決定離開釉衣村,到外面的大都市去打拼,看能不能趁年輕有所作為。結果一去竟然是十個寒暑音訊全無,家鄉的老父母到處打聽都求不到永強的音訊。老父母只好拜託了村裡小時候跟永強最要好的劉英姑娘幫忙找永強。
這個劉英姑娘也正值適婚年齡,家裡一雙父母挺和樂的,但父母也在擔心她沒有一門好親事,於是留英家裡就同意這個尋找永強的請求,讓劉英也暫時離開村子去追查永強的下落。這劉英一個女孩子事親至孝,不像永強從小就想著幹大事當大官,劉英出村只想找人,不想多逗留,能早點回去孝順父母才是他的自我期許。
於是劉英出發了,可是釉衣村之外的世界已經歷經好幾次迭代更新了,不光是街道名稱變了,交通系統也是改了又改,即便永強這十年在外頭的住所都沒變,劉英想找到永強也是千難萬難。然後就是我們看到的至今劉英也音訊全無。常言道,生要見人、屎要見屍,如果劉英找到了永強,劉英肯定會回釉衣村裡,她不但認得路而且時時刻刻歸心似箭,所以這個完成任務之後的回程也不會是個問題。那麼唯一的可能,就是劉英至今還沒找到永強。
現在大家都了解了吧,問題出在被代理的HUGO找不到Commento,不是Commento被找到之後沒有路可以回來。至於永強為什麼這多麼多年來都不回釉衣村,那是另外一個故事了,為避免節外生枝,我們且看下回分解。
好,了解了問題所在之後,解決方法有很多種,這邊使用一個比較簡單粗暴的方式,我將他取名為『自己不能越獄,那就把別人也拉進監獄』:我們要做的事情就是用NginX把Commento也代理了,如果Commento跟HUGO都被代理,而且也都使用SSL連線,那麼他們就能跟以前一樣互相傳遞請求跟回應(都在NginX的子領域)。
現在我們打開瀏覽器,註冊一個no-ip網址給Commento server,我們把這個網址用代號<the no-ip addr. for commento>代替,然後使用這個新網址取得SSL金鑰(記得要暫時關閉nginx,釋放certbot需要用到的埠80)
$ sudo systemctl stop nginx
$ certbot certonly --standalone -d <the no-ip addr. for commento>
緊接著再設定一次Commento,在~/.bashrc裡面我們修改COMMENTO_ORIGIN=之後的baseURL為https://<the no-ip addr. for commento>,讓Commento以SSL連線跟HUGO溝通。然後我們再打開NginX的設定檔仿照上面兩個server區塊替Commento也做一份,都寫在同一個http {...}大區塊裡面
server {
listen 80;
server_name <the no-ip addr. for commento>;
return 301 https://<the no-ip addr. for commento>$request_uri;
}
server {
listen 443 ssl;
server_name <the no-ip addr. for commento>;
ssl_certificate /etc/letsencrypt/live/<the no-ip addr. for commento>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<the no-ip addr. for commento>/privkey.pem;
local / {
proxy_pass http://<the designated LAN ip>:<LAN port for commento>;
}
}
存檔離開之後,也要告知HUGO如果要請求產生留言區,就要透過新的no-ip網址使用SSL安全連線來訪問新的Commento,於是我們編輯HUGO主題裡的Commento Server位址
$ vi ~/<HUGO project>/<the site name>/themes/<your theme name>/layout/_default/single.html
把src="..."裡頭的那串網址更換成https://<the no-ip addr. for commento>/js/commento.js,存檔離開。接著重啟NginX與Commento兩個伺服器。
$ source ~/.bashrc; ~/<commento project>/build/prod/commento
$ sudo systemctl restart ngix
這樣一來就能完成新的伺服器配置如下圖所示,而且你的部落格網站同時擁有SSL安全連線與留言區啦。
![]() |
|---|
| New servers' deployment |

