⚠ Port 443 of this computer does not appear to be accessible using hostname: metabolism.logophilia.eu ----

Thanks for the feedback, really appreciate the time. Not to be that smartass, but:

[2/5] Preparing configuration 
✓ Ports 80 and 443 are free for use 

It does initially say “free for use”. That said, I have my instance up and running now, with the help of Gemini (mostly about docker usage of all things lol). I would like to offer my “runbook” for any other Virtualmin users out there, because: “free the 443 port” is not the solution here. The rest of my post will be that, if I should rather post it somewhere else, like a new thread, please tell me where to go; I think I’m not the only one with this setup, and it might be useful for others. Thanks again!

So, after the setup has been completed with

cd /var/discourse
./discourse-setup --skip-connection-test

(c/o @darkpixlz :purple_heart:)

… you would do the following steps when on a VPS managed via Webmin and Virtualmin, and using a subdomain.

The Definitive Virtualmin + Discourse Runbook *

  • (1) Cleaning up remnants (If retrying, like I was):

    rm -rf /var/discourse/shared/standalone/ssl/*

    rm -rf /var/discourse/shared/standalone/letsencrypt

    rm -rf /var/discourse/shared/standalone/state

  • (2) Template Deletion:

    You must completely delete the templates/web.ssl.template.yml and templates/web.letsencrypt.ssl.template.yml lines from app.yml. The custom launcher parser will evaluate them even if prefixed with #.

  • (3) Email Setup & Variables:

    Change DISCOURSE_SKIP_EMAIL_SETUP from '1' to '0', because your Discourse won’t be able to connect and check DiscordID;

    Add DISCOURSE_FORCE_HTTPS: true so the backend generates secure URLs.

    Friendly reminder: Ensure your DISCOURSE_SMTP_USER_NAME is set to your raw mailbox account name (e.g., 'logophilia'), not the full email address (e.g., 'logophilia@logophilia.eu'), and wrap the credentials in single quotes (') to bypass potential YAML character parsing bugs.

  • (4) Expose Block Configuration:

    Ensure your expose: block in app.yml contains a HTTP mapping; mapping 443=>8443 is optional / redundant, since Virtualmin terminates the SSL logic before passing it along:

    expose:
      - 8080:80
    

    you can start rebuilding now:

    cd /var/discourse
    ./launcher rebuild app
    
  • (5) Subdomain and Proxy Path Setup:

    • Create your subdomain in Virtualmin as usual, and secure it with a Let’s Encrypt SSL certificate (done automatically, just saying: make sure it doesn’t error out for whatever unrelated reason).
    • Navigate to Proxy Paths (Virtualmin → your subdomain → Web Configuration → Proxy Paths), create a new mapping / to http://localhost:8080/, keep “serve locally” unchecked, but toggle Proxy WebSocket to Yes to allow real-time updates and notification streams.
  • (6) CSRF Header Directives:

    • In Webmin ➔ Servers ➔ Apache Webserver ➔ [find your subdomain config here and click on the config for 443] ➔ Edit Directives, place the following lines right above Virtualmin’s own proxy block for Let’s Encrypt (usually “ProxyPass /.well-known !”) to facilitate CSRF token forwarding:
    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
    

    ProxyPreserveHost On: Tells Discourse your actual domain name instead of “localhost”.
    RequestHeader set X-Forwarded-Proto "https": Explicitly tells Discourse the user is using a secure connection, matching up with your DISCOURSE_FORCE_HTTPS: true setting.
    RequestHeader set X-Forwarded-For: Passes the visitor’s real IP address to the container so security logs work.

  • (7) Clean Container Handshake:

    While the long (sorry … it’s true though;-) rebuild process finishes, ensure any potentially stuck container blueprint is wiped with docker rm -f app so that running ./launcher start app spins up a completely fresh instance bound to port 8080. Check that docker ps shows something under “ports” similar to:

    # docker ps
    CONTAINER ID   IMAGE                 COMMAND        CREATED          STATUS          PORTS                                                                                NAMES
    d21772a21e36   local_discourse/app   "/sbin/boot"   45 minutes ago   Up 45 minutes   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp, 0.0.0.0:8443->443/tcp, [::]:8443->443/tcp   app
    

    (As you can see, I left the 443=>8443 directive in my app.yml, works either way.)

  • (8) Monitoring & Launch:

    Tail the boot stream with docker logs -f app until the database migrations finish and the workers start processing requests. A bunch of “INFO” lines in quick succession, basically.

  • (9) Finalization:

    Load your subdomain in a browser, click Register, and let the system send a validation email to your mailbox.

*) until proven wrong :wink: