Win-ACME ‘Preliminary validation failed’ with error ‘(null)’ during express server HTTP-01 challenge

I’m currently trying to get an SSL certificate through win-acme and getting the error ‘Preliminary validation failed, the server answered ‘(null)’.
I am experiencing the problem that null is returned and I can’t authenticate.

First, before using win-acme, I connected internal ports 80 and 443 via port forwarding to my public IP(www.hansol.monster) by connecting to 192.168.0.1.

After purchasing the domain, I ran a server on port 80 to connect the public IP to the domain and verified that the www.hansol.monster page was displayed.

To use WIN-ACME, I set the firewall to allow both inbound and outbound rules for 80/443 and shut down Windows Security System.

Here is my process using win-acme.

 A simple Windows ACMEv2 client (WACS)
 Software version 2.2.6.1571 (release, pluggable, standalone, 64-bit)
 Connecting to https://acme-v02.api.letsencrypt.org/...
 Connection OK!
 Scheduled task not configured yet
 Please report issues at https://github.com/win-acme/win-acme

 N: Create certificate (default settings)
 M: Create certificate (full options)
 R: Run renewals (0 currently due)
 A: Manage renewals (0 total)
 O: More options...
 Q: Quit

 Please choose from the menu: m

 Running in mode: Interactive, Advanced
 Source plugin IIS not available: No supported version of IIS detected.

 Please specify how the list of domain names that will be included in the
 certificate should be determined. If you choose for one of the "all bindings"
 options, the list will automatically be updated for future renewals to
 reflect the bindings at that time.

 1: Read bindings from IIS
 2: Manual input
 3: CSR created by another program
 C: Abort

 How shall we determine the domain(s) to include in the certificate?: 2

Description:         A host name to get a certificate for. This may be a
                     comma-separated list.

 Host: www.nnx.monster

 Source generated using plugin Manual: www.nnx.monster

 Friendly name '[Manual] www.nnx.monster'. <Enter> to accept or type desired name: <Enter>

 By default your source identifiers are covered by a single certificate. But
 if you want to avoid the 100 domain limit, want to prevent information
 disclosure via the SAN list, and/or reduce the operational impact of a single
 validation failure, you may choose to convert one source into multiple
 certificates, using different strategies.

 1: Separate certificate for each domain (e.g. *.example.com)
 2: Separate certificate for each host (e.g. sub.example.com)
 3: Separate certificate for each IIS site
 4: Single certificate
 C: Abort

 Would you like to split this source into multiple certificates?: 1

 The ACME server will need to verify that you are the owner of the domain
 names that you are requesting the certificate for. This happens both during
 initial setup *and* for every future renewal. There are two main methods of
 doing so: answering specific http requests (http-01) or create specific dns
 records (dns-01). For wildcard identifiers the latter is the only option.
 Various additional plugins are available from
 https://github.com/win-acme/win-acme/.

 1: [http] Save verification files on (network) path
 2: [http] Serve verification files from memory
 3: [http] Upload verification files via FTP(S)
 4: [http] Upload verification files via SSH-FTP
 5: [http] Upload verification files via WebDav
 6: [dns] Create verification records manually (auto-renew not possible)
 7: [dns] Create verification records with acme-dns (https://github.com/joohoi/acme-dns)
 8: [dns] Create verification records with your own script
 9: [tls-alpn] Answer TLS verification request from win-acme
 C: Abort

 How would you like prove ownership for the domain(s)?: 1

Description:         Root path of the site that will serve the HTTP validation
                     requests.

 Path: C:songhansolnnx_2023_storekiosk_server

Description:         Copy default web.config to the .well-known directory.
Default:             False
Argument:            False (press <Enter> to use this)

 Copy default web.config before validation? (y/n*) - no

 After ownership of the domain(s) has been proven, we will create a
 Certificate Signing Request (CSR) to obtain the actual certificate. The CSR
 determines properties of the certificate like which (type of) key to use. If
 you are not sure what to pick here, RSA is the safe default.

 1: Elliptic Curve key
 2: RSA key
 C: Abort

 What kind of private key should be used for the certificate?: 2

 When we have the certificate, you can store in one or more ways to make it
 accessible to your applications. The Windows Certificate Store is the default
 location for IIS (unless you are managing a cluster of them).

 1: IIS Central Certificate Store (.pfx per host)
 2: PEM encoded files (Apache, nginx, etc.)
 3: PFX archive
 4: Windows Certificate Store (Local Computer)
 5: No (additional) store steps

 How would you like to store the certificate?: 2

Description:         .pem files are exported to this folder.

 File path: C:songhansolnnx_2023_storekiosk_server

Description:         Password to set for the private key .pem file.

 1: None
 2: Type/paste in console
 3: Search in vault

 Choose from the menu: 1

 1: IIS Central Certificate Store (.pfx per host)
 2: PEM encoded files (Apache, nginx, etc.)
 3: PFX archive
 4: Windows Certificate Store (Local Computer)
 5: No (additional) store steps

 Would you like to store it in another way too?: 5

 Installation plugin IIS not available: No supported version of IIS detected.

 With the certificate saved to the store(s) of your choice, you may choose one
 or more steps to update your applications, e.g. to configure the new
 thumbprint, or to update bindings.

 1: Create or update bindings in IIS
 2: Start external script or program
 3: No (additional) installation steps

 Which installation step should run first?: 3

 Plugin Manual generated source www.nnx.monster with 1 identifiers
 Plugin Domain created 1 order
 Cached order has status invalid, discarding
 [www.nnx.monster] Authorizing...
 [www.nnx.monster] Authorizing using http-01 validation (FileSystem)
 Answer should now be browsable at http://www.nnx.monster/.well-known/acme-challenge/gT_wY7_hBHd3A_AYgThZECte1arH2GA36iNIN6RwH5s
 Preliminary validation failed, the server answered '(null)' instead of 'gT_wY7_hBHd3A_AYgThZECte1arH2GA36iNIN6RwH5s.VoVW-ROfT0SMRJyquVopjoZ-pENzG0zjNjem8VenAcM'. The ACME server might have a different perspective
 [www.nnx.monster] Authorization result: invalid
 [www.nnx.monster] {"type":"urn:ietf:params:acme:error:unauthorized","detail":"75.2.0.44: Invalid response from http://www.nnx.monster/.well-known/acme-challenge/gT_wY7_hBHd3A_AYgThZECte1arH2GA36iNIN6RwH5s: 403","status":403,"instance":null}
 [www.nnx.monster] Deactivating pending authorization

 Create certificate failed, retry? (y/n*)

The strange thing is that it creates a ‘.well-known/acme-challenge’ folder and an authentication file in the path you set when starting the last process (verification),
You can see that it creates a text file like ‘gT_wY7_hBHd3A_AYgThZECte1arH2GA36iNIN6RwH5s.VoVW-ROfT0SMRJyquVopjoZ-pENzG0zjNjem8VenAcM” which is output in the example above.

However, the generated file is unreadable and returns null.

I’ve tried running it on different ports, including different 80/443/8081 ports, I’ve tried disabling the firewall, I’ve tried searching, but I can’t figure out what the problem is.

Here’s a simple express server code we created for SSL authentication.

// test.ts
import dotenv from "dotenv";
dotenv.config();

import express from "express";

const httpPORT = Number(process.env.HTTPPORT) || 80;

const app = express();
app.use(express.json());;

import cors from "cors";
app.use(cors());

app.use((req, res, next) => {
    const requestURL = req.url;
    console.log(`URL: ${requestURL}`);
    next();
});

app.use(express.static(__dirname));
app.get('/', (req, res) => {
    res.redirect('./index.html');
});

app.listen(httpPORT, () => {
    console.log(`Server listening on HTTP port ${httpPORT}`);
});

What am I missing?