Port Igniter
Get Started

Troubleshooting

Common problems, such as unstyled pages, sign-ins that won't stick, missing emails, payments that don't grant access, and failed uploads, and how to fix them.

LMS Support Home

APP_BASE_URL doesn’t match the address you’re using. Stylesheets and scripts are requested from that address, and links in emails use it too. Set it to the exact address people use, including the scheme, the port, and the trailing /, then run docker compose up -d.

The most common mismatch is the scheme. The stack only serves plain HTTP, so APP_BASE_URL=https://127.0.0.1:7080/ produces an unstyled page: the page loads over HTTP, but its stylesheets are requested over HTTPS, which nothing answers. Use http://127.0.0.1:7080/ until a reverse proxy handles HTTPS for you.

To see which address the page asks for, run:

curl -s http://127.0.0.1:7080/ | grep -o 'href="[^"]*\.css[^"]*"'

docker compose up says “Set MARIADB_ROOT_PASSWORD in .env”

The database passwords have no defaults on purpose. Fill in MARIADB_ROOT_PASSWORD and MARIADB_PASSWORD in .env, along with MARIADB_DATABASE and MARIADB_USER.

The site won’t start after editing .env

Check the php logs with docker compose logs php. The usual causes:

  • A value with spaces isn’t quoted. Write email.fromName = 'My Academy', not email.fromName = My Academy.
  • A comment follows a value on the same line. Move comments onto their own lines.

The application can’t reach the database

The php container waits up to 60 seconds for MariaDB on start. On a slow server, the first start can take longer while the database initializes. Raise LMS_DB_WAIT_SECONDS and run docker compose up -d again.

If you changed a MARIADB_* password in .env after the first start, the database still has the old one: those values only apply when the database is first created. Put the original values back, or change the password inside MariaDB to match.

I can’t sign in as the administrator

  • Sign in with the email address, not a username.

  • The administrator is only created when no active administrator exists. If you changed lms.adminEmail or lms.adminPassword after the first start, the original account still exists with its original details.

  • The password must be at least 12 characters, or the account isn’t created. The site still starts normally, so this is easy to miss. Look for this line in docker compose logs php:

    lms.adminPassword must be at least 12 characters.
    

    To fix it, set a longer lms.adminPassword in .env, then recreate the php container so it picks up the change. The administrator is created on start:

    docker compose up -d --force-recreate php
    

Signing in doesn’t stick, or forms say the page expired

  • If you set cookie.secure = true, the site must be reached over https://. Signing in over plain HTTP won’t stick.
  • If you’re behind a proxy, make sure APP_BASE_URL uses the address visitors actually see.

Every visitor seems to come from the same IP address

Sign-in throttling is per IP address. If another proxy, such as Traefik, Cloudflare, or a load balancer, sits in front of the stack, PortIgniter LMS sees the proxy’s address for everyone, and one person’s failed sign-ins can lock out others. List the proxy’s address or range in app.proxyIPs. See Running behind HTTPS.

The site redirects in a loop

app.forceGlobalSecureRequests = true needs your proxy to forward the X-Forwarded-Proto header, and the proxy must be listed in app.proxyIPs if it isn’t the stack’s own nginx. Otherwise, turn the setting off and let your proxy handle the redirect to HTTPS.

Nobody receives emails

Check the email.* settings in .env. Match the port and encryption: port 587 uses tls, and port 465 uses ssl. Quote the sender name if it contains spaces. Failed sends are recorded in docker compose logs php and in the application log under writable/logs.

Also check the recipient’s spam folder, and make sure your sending domain has SPF and DKIM records for your mail provider.

A payment went through, but the learner wasn’t enrolled

Access is normally granted when Stripe’s webhook arrives, and again when the buyer returns to the success page. If neither happened:

  • In the Stripe Dashboard, open Developers > Webhooks and check the endpoint’s recent deliveries. A 400 response means the signing secret doesn’t match. Copy the right whsec_... secret for the current mode into .env.
  • Make sure the endpoint is https://YOUR-SITE/webhook/stripe and subscribed to every event listed in Selling Courses.
  • Remember that test mode and live mode have separate webhooks and separate secrets.

Stripe retries failed deliveries, and each event is processed only once, so it’s safe to resend an event from the Stripe Dashboard after fixing the problem.

There are no Stripe keys for the current mode. If stripe.testMode = true, fill in the test keys. If it’s false, fill in the live keys.

A paid course has no Buy button and shows no price

The course’s Who can enroll setting is By invitation only. Set it to Open and publish the course to sell it.

Learners say a lesson is locked

Locked items explain what’s needed to open them. Check the course’s Order of learning and the item’s unlock rules in the builder. If a learner should be let through, review their progress under Reports. Remember that optional items never block anyone.

Publishing fails

The builder lists every problem under Fix these before publishing, with a link to each. Common ones:

  • An image or video references a file that was removed from the course.
  • A random quiz pull asks for more questions than its bank can supply after its filters.
  • An unlock rule points at an item that was deleted, or two items wait on each other.
  • An assignment has no instructions.

A video or file upload fails

  • Check the file is a supported type and under the limit. See Files and limits. Raise lms.videoMaxMb for larger videos.
  • Uploads are checked by content. A file renamed to a different extension is rejected.
  • Text files containing code, such as a Markdown cheat sheet full of commands, can be detected as scripts. Upload them as PDF instead.
  • If you run a reverse proxy in front of the stack, make sure it allows request bodies as large as your largest video.

The editor says to reload

Someone else saved the same course while you were editing it, possibly you in another tab. Reload to get the latest version. The builder stops you here so nobody’s changes are silently overwritten.

Two-step sign-in codes are always rejected

  • Make sure the device’s clock is set automatically. Codes depend on the time.
  • If encryption.key was changed or lost after people turned on two-step sign-in, their secrets can no longer be read. An administrator needs to click Turn off two-step sign-in for each affected user under Admin > Users, and they can then set it up again.

Reporting a bug

Include the output of:

docker compose exec php php spark system:info

along with what you did, what you expected, and any errors from docker compose logs php.

Top