Support my work ♥

How to secure a hardened, minimal QNAP installation with letsencrypt

There is a simple rule for hardening a system: You can not hack what is not there.

With this in mind, removing as much software from a system as possible makes it simpler to understand and then control.

For this guide you will need a DNS name. You can get one for free from deSEC or register your own.

Why do this extra work?

Because QNAP has a terrible track record on security. They trade more features for understandability. Violating the KISS principle or Economy of mechanism a key part of system security.

Some of the last attacks were using the default webserver to access privileged services. Disabling it closes that attack vector.

Setup NAS

Assign a static IP

Assign a static IPv4 and a IPv6 address to all the adapters or bridges you use. Even if you use private IPs only, the DHCP server can forget about your current lease and assign the NAS a new IP and then a lot of stuff breaks.

Side note: It is good practice to have your static (server) assignments outside of your dynamic range.


  • Router: ; fd02:cafe::1/64
  • Dynamic Range: - ; fd02:cafe::d:0 - fd02:cafe::d:ff
  • Static Range: - ; fd02:cafe::10 - fd02:cafe::ff

Or use a globally unique IPv6 range from ISP or your datacenter (eg. ipv6onlyhosting) and never worry about overlapping ip ranges again when connecting networks (with VPN).

Remove or Stop already installed apps

Go to App Center then remove everything you don't need except:

  • QuFirewall (install it if you don't have it)
  • License Center (sadly)
  • Malware Remover
    • Disable periodic scans
    • Set to auto update
    • Enable scan on update
  • Network & Virtual Switch
  • Notification Center
  • QTS SSL Certifiacte
  • QuLog Center
  • Resource Monitor

Most people will also keep * Container Station (docker with docker-compose) * Virtualization Station (kvm)

Everything else can go or be replaced by containers like replace Video Station with jellyfin in a container

Minimal Configuration

Go to ControlPanel -> System and set the following settings:

  • General Settings
    • Enable secure connection (HTTPS)
    • TLS version: 1.2 and later
    • Enable strong cipher suites
    • Port number: 443

Enable SSH Access on QNAP

Login with an admin account to your NAS

Then open ControlPanel -> Network & File Services -> Telnet / SSH

Now enable:

  • Allow SSH connection (Only administrators can login remotely.)
  • Port number: 22
  • Enable SFTP

Open Firewall (a bit)

Here we need two firewalls to work in agreement.

Router or Edge Firewall

This is the firewall or router between your NAS and the internet. If you don't have one already, you should get one now!


Using an old PC with a PCIe Ethernet card is an okay option.

If you can get an APU2 or a Turris or a mikrotik, even better.


opnsense (OSS) or pfsense (mostly OSS) or RouterOS from MicroTik (pay one time) are popular choices these days.


  1. Forbid all incoming traffic as the last rule.
  2. Then add an exception for HTTPS or port tcp/443
  3. For the duration of confirming the certificate add an exception for HTTP or port tcp/80

Both rules are

  • IN rules that allow from
  • any source from IPv4 or IPv6 with
  • source port range 1025..65535
  • optional: rate limit new connections to 20 per 90 seconds

On Device Firewall QuFirewall

You can get the QuFirewall in the App Center for free.

Open the QuFirewall application and Add a new profile.

Then add all the IPv4 and IPv6 ranges that are allowed to use the NAS es services.

  • Include Samba (the windows shares) on port 445 (probably only to LAN IPs)
  • HTTP on port 80 (from the everywhere and ::/0)
  • HTTPS on port 443 (also from everywhere
  • And all the ports you expose from containers

Important: KVM virtual machines with bridged interfaces need their own firewalls

Compile the binary

/// Copyright (C) 2022 Stefan Schindler

package main

import (

/// get: `go tool dist list`
/// current: `go env GOOS GOARCH`
/// build: `go build -tags netgo ./http_well_known.go`
/// serve /mnt/HDA_ROOT/.config/QcloudSSLCertificate/cert/.well-known for letsencrypt
func main() {
    argsWithoutProg := os.Args[1:]

    bind_addr := ":80"
    if len(argsWithoutProg) == 1 {
        bind_addr = argsWithoutProg[0]
    fmt.Printf("binding to \"%s\"\n", bind_addr)

    newDir, _ := os.Getwd()
    fmt.Printf("Current Working Direcotry: %s\n", newDir)

    http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
        fmt.Printf("%s: %s\n", r.Method, r.URL)
        fmt.Fprintf(w, "Welcome to my website!\n")

    fs := http.FileServer(http.Dir("./"))
    shifted_fs := http.StripPrefix("/.well-known/", fs)
    // http.Handle("/.well-known/", shifted_fs)
    // Docs:
    http.HandleFunc("/.well-known/", func (w http.ResponseWriter, r *http.Request) {
        fmt.Printf("%s: %s\n", r.Method, /*r.RequestURI*/ r.URL)
        shifted_fs.ServeHTTP(w, r)

    res := http.ListenAndServe(bind_addr, nil)
    fmt.Printf("%s\n", res)

Save this source code in your local user home under ~/go/bin in http_well_known.go, you may have to create the directories.

Then compile it with this command:

go build -tags netgo ./http_well_known.go

Transfer the binary

Note the : at the end is mandatory.

scp http_well_known

Get/Renew the certificate

First login with ssh on your machine:


Qnap hides the fact that you can have an interactive shell in the options. The 8th invisible option is the sh shell.

Press 8 and then confirm with y + Enter

Now run the server with ./http_well_known it will output any access that takes place so you have a log if you are attacked meanwhile.

Temporarily open all the firewalls

Don't forget to open the all firewalls between the internet and the NAS to allow:

  • IPv4 and IPv6
  • Source ports 1025..65535
  • Target ports 80, 443

Start the process in the WebUI

Go to ControlPanel -> System -> Security -> SSL Certifiacte & private Key. Then either:

  • Press Certifiacte Renewal if the system is configured already
  • Press Replace Certifiacte if you want to add/change the TLS common/domain name
    • Select Get from letsencrypt in the dropdown
    • Enter the domain name, eMail and all alternative names

Now you should see the network verify the challenge and verify the certificate:

# ./http_well_known 
binding to ":80"
Current Working Direcotry: /mnt/HDA_ROOT/.config/QcloudSSLCertificate/cert/.well-known
GET: /.well-known/acme-challenge/q70a7XfBD6VVcno666666d2pD9abcdefICb3QH3Lfvw
GET: /.well-known/acme-challenge/q70a7XfBD6VVcno666666d2pD9abcdefICb3QH3Lfvw
GET: /.well-known/acme-challenge/q70a7XfBD6VVcno666666d2pD9abcdefICb3QH3Lfvw
GET: /.well-known/acme-challenge/q70a7XfBD6VVcno666666d2pD9abcdefICb3QH3Lfvw
GET: /.well-known/acme-challenge/q70a7XfBD6VVcno666666d2pD9abcdefICb3QH3Lfvw

After the new certificate is installed you can exit the http_well_known process by entering Ctrl + C and then Ctrl + D.


Under ControlPanel -> System -> General Settings -> System Administration enable:

  • Force secure connection (HTTPS) only

Close all the firewalls

  • Close the QuFirewall
  • Close the firewall between the internet and the LAN