Today I worked on a WordPress-based academy site that already worked, but had the kind of small issues that slowly turn into operational debt.
The favicon was still WordPress. Students could land in the WordPress back office after login. Tutor LMS had a frontend dashboard, but it wasn't wired into the login flow. A few plugins had security updates waiting. XML-RPC was still reachable. The checkout looked broken because no card form appeared on the page.
None of that needed a redesign. It needed a controlled maintenance pass.
The rule was simple: inspect first, back up, change one thing at a time, verify every change.
The first pass
The site had a few visible problems:
- the browser tab still used the default WordPress favicon;
- students could end up in
/wp-admin/; - the Tutor LMS dashboard existed but wasn't the default student destination;
- Stripe looked incomplete at checkout;
- plugins had pending security updates;
- XML-RPC answered requests;
- WordPress exposed version metadata in the page HTML;
- basic security headers were missing.
Each item was small. Together they said the same thing: the site needed quality control, not another layer of polish.
Backups before changes
Before touching anything, I backed up the database and wp-content.
That matters with WordPress. A site includes plugin code, uploads, database options, serialized settings, premium plugin state, cache, and a few surprises you only discover when you try to roll back.
I also made targeted backups before editing the small custom branding plugin. That plugin became the safest place for redirects, headers, favicon-related behavior, and checkout copy.
The favicon
The full Trabitat Academy logo was not a good favicon. It had text, a vertical layout, and too much detail for 16 or 32 pixels.
I used the geometric Trabitat mark instead, cropped it tightly into a square, uploaded it as the WordPress site_icon, and checked the rendered HTML. The page no longer pointed to the default WordPress logo.
A favicon is tiny, but it is one of those details that tells you whether a site is finished or still running with template leftovers.
Student login
Tutor LMS already had the right feature: a frontend student dashboard.
The problem was the WordPress login flow. It could still send a student toward the admin area. That is a bad experience, and it makes the product feel unfinished.
I registered /escritorio/ as the Tutor dashboard and added two rules:
- non-admin users go to
/escritorio/after login; - non-admin users who try
/wp-admin/are sent back to the dashboard.
Admins and staff still keep normal back office access. Students also lose the black WordPress admin bar on the frontend.
I tested it with a temporary user: real login, real dashboard, Tutor menu visible, WordPress admin bar gone. Then I deleted the user.
Plugin updates and hardening
The security pass had two parts: update what needed updates, then remove obvious exposure.
Tutor LMS moved from 3.9.11 to 3.9.12. Tutor Pro moved to the same version. Royal MCP moved from 1.4.22 to 1.4.26. Akismet was inactive, but I updated it anyway. The default WordPress themes were updated too.
The WordPress upgrader returned without an error, but the versions did not change inside the container. That was the first useful reminder of the day: a quiet command is not the same as a successful command.
I used the official package URLs from the WordPress update transients, replaced the plugin directories in a controlled way, and moved the old copies out of the webroot.
I also removed stale tutor-pro.backup.* directories from wp-content/plugins. Inactive plugin code can still be reachable as files. If you need it for rollback, keep it in a backup directory, not in the public tree.
Then I added basic hardening:
- XML-RPC returns 403;
wp_generator, RSD, and WLW links are removed;- HSTS,
X-Content-Type-Options,X-Frame-Options,Referrer-Policy, andPermissions-Policyheaders are present; - Hello Dolly is gone.
The checks were boring in the best way: no pending plugin or theme updates, public pages returned 200, the course page still loaded, Stripe was still active, XML-RPC was blocked, and the headers were present.
The checkout problem
The checkout was the part that looked broken.
On the Academia checkout page, Stripe appeared as a payment method, but there was no card form. At first glance that looks like a missing Stripe integration.
It wasn't.
Tutor Stripe uses Stripe Hosted Checkout. The card form is not supposed to appear inside WordPress. The flow is:
- add the course to the cart;
- open
/checkout/; - fill in billing details;
- accept the terms;
- continue to payment;
- WordPress creates the Stripe checkout session;
- the browser lands on
checkout.stripe.com, where the card form appears.
The problem was the copy. The page said “Stripe” and “Pay now,” but it did not explain that the card form would open on Stripe's hosted page. If a required billing field was empty, the browser stopped the submit with a generic validation bubble. That made the checkout feel broken.
I changed the button text to:
Continue to secure Stripe payment
And added a short note below the payment method:
Complete your billing details and continue. The card form will open on Stripe's secure page; Academia Trabitat does not store card details.
Then I tested the whole flow with a temporary student: add course to cart, open cart, open checkout, fill fake billing data, accept terms, continue, land on Stripe Hosted Checkout, see the card form. I stopped there. No card data, no payment.
Turning the fix into a monthly check
After the fixes, I added a monthly QA run.
Once a month it checks:
- WordPress and plugin versions;
- pending updates;
- home, login, dashboard, course, cart, and checkout;
- real student login with a temporary user;
- redirect to Stripe Hosted Checkout;
- security headers;
- XML-RPC status;
- favicon and basic branding;
- cleanup of the temporary user.
This is the part that keeps the work from depending on memory. WordPress sites drift. Plugins change. Checkout behavior changes. A monthly check catches small breaks before a real student finds them.
The actual lesson
The work reduced uncertainty around the parts that matter: login, security, checkout, and rollback.
Every change needed a receipt:
- favicon: HTML pointed to the new icon;
- login: a student landed on the Tutor dashboard;
- security: versions, headers, and XML-RPC were checked;
- checkout: Stripe Hosted Checkout was reached without paying;
- QA: the monthly run was scheduled.
That is the only way I trust WordPress changes in production: small edits, backups first, and verification after every step.
WordPress can be fragile. It is much less fragile when you stop treating “the page loads” as proof that the system works.