Migrating from GitHub Pages to GitLab Pages with HTTPS
Background
I wanted HTTPS for my GitHub Pages blog. CloudFlare helped, but browsers still complained about the certificate. GitHub Pages only supported HTTPS on the default github.io
domain at the time (custom domains are supported now), so I moved to GitLab Pages, which lets you upload your own cert.
My blog runs on Hexo.
Migrating the Pages project
Sign up for GitLab and create a project named yourname.gitlab.io
. You can make it private—nice compared to GitHub’s public-only limitation for free users.
GitLab can import GitHub repositories, but do not simply import your old Hexo repo; GitLab and GitHub treat Hexo differently. Build a fresh Hexo project locally instead, then copy over the source
folder and _config.yml
. The last missing piece is .gitlab-ci.yml
.
(The official Hexo template repository on GitLab was empty when I migrated, so cloning it went nowhere.)
.gitlab-ci.yml
GitHub Pages relies on hexo g
+ hexo d
: generate the static files locally and push them. GitLab Pages can generate the static site on the server by running the scripts defined in .gitlab-ci.yml
. In theory that is convenient; in practice the shared runners can be slow, taking hours to rebuild minor changes.
Here is the config I used (adapted from a community example—the official snippet included an unnecessary hexo deploy
):
1 | # Full project: https://gitlab.com/pages/hexo |
Push content
Add your SSH key in GitLab and push the repository. No need to run hexo
locally because the pipeline generates public/
for you. If you copied directories from the old repo, make sure to clear their original git history—otherwise Git will refuse to push.
Pipelines
After pushing, GitLab runs the pipeline defined in .gitlab-ci.yml
. Check the Pipelines tab for status: pending
, running
, passed
, or failed
. pending
and running
can take a while. Click a job to view its console output (use the Raw button for plain text).
If a job fails, read the log. For example, Git complained about missing user info:
1 | *** Please tell me who you are. |
Set those values in the pipeline before continuing.
Configure a custom domain and HTTPS
DNS
Add a CNAME (www
) pointing to yourname.gitlab.io
. GitLab provides verifiable DNS records; use them to prove domain ownership.
Let’s Encrypt validation
I generated certificates with Certbot on a VPS that held my DNS zones:
sudo ./letsencrypt-auto certonly --manual -d yoursite.com -d www.yoursite.com
Certbot prints the token needed for the HTTP-01 challenge. There are two ways to publish it.
Method 1 (one-off): add commands to .gitlab-ci.yml
that create the challenge file under public/.well-known/acme-challenge/
. Commit, push, wait for the pipeline, then continue the verification.
Method 2 (easier to reuse): install hexo-processor-static
so everything under source/_static
is copied verbatim to public/
.
Add to .gitlab-ci.yml
:
- npm install hexo-processor-static --save
Create source/_static/.well-known/acme-challenge/<token>
containing the validation string, then push. Once the pipeline finishes, rerun the validation on the server.
Enable HTTPS in GitLab
Certbot stores the cert and key at:
1 | /etc/letsencrypt/live/YOURDOMAIN/fullchain.pem |
In your GitLab project go to Settings → Pages, remove the old domain if necessary, and add it again with HTTPS enabled. Copy the contents of fullchain.pem
into “Certificate (PEM)” and privkey.pem
into “Key (PEM)”. After a short delay https://YOURDOMAIN
should be live.
Hexo theme tweaks
Force HTTPS redirects in the NexT theme by editing layout/_layout.swig
and adding inside <head>
:
1 | <script type="text/javascript"> |
Tell search engines to prefer HTTPS with a canonical link:
<link rel="canonical" href="https://YOURDOMAIN/specific/page" />
Commit and push to trigger another build.
Renew certificates
Let’s Encrypt certificates expire every 90 days. Repeat the validation steps to renew, or automate it with gitlab-letsencrypt:
1 | npm install -g gitlab-letsencrypt |
References: