Blog

  • PHP 8.6 Ships on November 19: What to Check Before Your Host Switches You

    PHP 8.6 Ships on November 19: What to Check Before Your Host Switches You

    PHP 8.6 is scheduled to reach general availability on November 19, 2026, and I will not be installing it that week on any site I run. Not because the release is dangerous — it is a steady, boring release — but because on a WordPress site the PHP version is usually chosen by your host, not by you, and that is where the surprise comes from. Some hosts move every account onto the new branch within days of a release. Others leave you on an old branch until it stops getting security fixes. Either default can break a site nobody tested.

    Here is the check I run, in the order I run it.

    The short version

    • PHP 8.6 is scheduled for general availability on November 19, 2026.
    • WordPress 7.1 is tested and compatible through PHP 8.5. PHP 8.6 is not in the compatibility matrix yet.
    • The deadline that matters more is December 31, 2026 — the day PHP 8.2 stops receiving security fixes.
    • Confirm your real version in Tools → Site Health → Info → Server, not on your host’s feature page.
    • Test on a copy of the site with every error switched on, then read wp-content/debug.log.
    • Plugins break first. WordPress core almost never does.

    What 8.6 changes, and what it does not

    The new features are developer features: readonly property defaults, partial function application, a built-in clamp(), a Time\Duration class, better JSON error messages. None of that touches your front page. If you run a small site, you will never type any of it.

    What can touch you is the third category: code that was always sloppy and is now officially deprecated. PHP 8.6 deprecates things like returning a value from a constructor, a return inside a finally block, the third argument of define(), and several old type-check helpers. It also changes behaviour in a few places that matter quietly — session cookies now default to HttpOnly and SameSite=Lax, and trim() now strips form-feed characters by default.

    Deprecations do not break code on day one. They fill your error log and, on a badly behaved host, they print notices into your page output. The things that actually break are plugins that never got tested against a new branch.

    Step 1: find out what you are really running

    Three places, and they can disagree:

    Site Health. In wp-admin, go to Tools → Site Health → Info and open the Server section. The PHP version there is the one your web server actually hands to WordPress. This is the number that counts.

    Your host panel. cPanel, Plesk and most managed dashboards have a “Select PHP version” control. Look at it, but do not trust it alone — a panel can list 8.4 while a different PHP handler serves your domain.

    SSH, if you have it. Run php -v. Be aware this shows the CLI binary, which is often a different version from the one FPM serves. Use it as a cross-check, not as gospel.

    Write the number down, along with today’s date. You want to be able to say “I checked on 11 November and it was 8.2” if something changes under you later.

    Step 2: ask your host one question

    One support ticket resolves most of this: do you upgrade PHP versions automatically, and when? There are three possible answers and they lead to different plans.

    “We upgrade at release.” You have eight days. Test now, because the switch is coming whether you prepared or not.

    “We upgrade after testing, a few weeks later.” You have a buffer, not a guarantee. The same test still applies, just with a longer runway.

    “You choose the version in the panel.” Nothing happens automatically. Your only real deadline is the PHP 8.2 end of security support.

    Ask in writing and keep the reply. When a site breaks at 2am after a maintenance window, “the panel said 8.4” is not an answer your host will accept — a ticket with their policy in it is.

    Step 3: know which deprecations touch your site

    This table is the short list worth grepping for. Everything here still works in 8.6; the point is to know whether it is your code or a plugin’s.

    Deprecated in 8.6 Where I have seen it What to do
    return inside finally Custom snippets, older child themes Move the return out of the block
    Third argument of define() Legacy plugins with case-insensitive constants Drop the argument, or replace the plugin
    spl_object_hash() Old helper libraries bundled into themes Use spl_object_id()
    mb_ereg* functions Plugins doing multibyte regex work Watch for it; the library behind it is end-of-life
    Session cookie defaults Custom login and cart flows outside WordPress Retest logins and checkouts on staging

    Notice what is not on the list: nothing WordPress core does. Core is tested against each new branch before the matrix is updated. The exposure is always the third-party code you installed in 2021 and forgot about.

    Step 4: test on a copy, with errors switched on

    Half an hour on a staging copy beats a week of guessing. Copy the site, point a subdomain at it, then do three things.

    First, turn on logging so deprecations show up instead of hiding. In wp-config.php, set WP_DEBUG and WP_DEBUG_LOG to true and WP_DEBUG_DISPLAY to false — you want the notices in a file, not printed over your pages.

    Second, switch the staging copy to the PHP version you are testing, then walk the site the way a visitor would: home page, one post, search, a form, the login page, and the checkout if you sell anything.

    Third, read the log. This is the part people skip:

    # what the staging site is running
    php -v
    wp core version
    wp plugin list --status=active --fields=name,version
    
    # calls that 8.6 deprecates, by file
    grep -rn "spl_object_hash\|mb_ereg\|is_double\|is_integer" \
      wp-content/plugins wp-content/themes
    
    # anything the debug log recorded
    grep -c "Deprecated" wp-content/debug.log

    An empty debug log after a full walkthrough is the result you want. A long list of deprecations from one plugin is an argument for replacing that plugin, not for staying on PHP 8.2 forever.

    Step 5: if you are on PHP 8.2, December 31 is your deadline

    PHP 8.2 goes into security-only support and then stops on December 31, 2026. After that date, a vulnerability found in 8.2 will not be patched. That is a much more concrete risk than a new release arriving on a date you can plan around.

    My recommendation is to skip 8.6 for now and target PHP 8.5. It is inside the WordPress compatibility matrix, its active support runs to the end of 2027, and security fixes run to the end of 2029. PHP 8.4 is also fine today, but its active support ends on the same day as 8.2’s security support, so moving to 8.5 gives you a longer runway for the same amount of work.

    Move to 8.6 later, after WordPress 7.2 — the release currently scheduled for December 8, 2026 — has been out for a few weeks and your plugin list has caught up.

    The order I follow

    Today: note your version, open the host ticket, and take a fresh backup before you change anything. If you have not applied WordPress 7.1.4 yet, do that first — that update is closing an actively exploited hole, and the same discipline applies: back up, block the upgrade directory, then update.

    This week: copy the site to staging, run it on the PHP version you are targeting, and read the log. If the site already feels slow on the current branch, measure it before you blame the new version — a PHP upgrade can fix a slow site, but it can also just move where the time goes.

    Before December 31: leave PHP 8.2. Then check the log again 48 hours after any switch. If a plugin throws notices, replace it while you still have a working version to fall back to, not after the host forces the change on a Friday.

    The whole job is one support ticket, one staging copy, and one look at a log file. That is a much better use of an afternoon than discovering on November 20 that your host moved you and your contact form stopped sending mail.

  • How to Update WordPress Core Without Opening an Attack Window

    How to Update WordPress Core Without Opening an Attack Window

    WordPress 7.1.4 is a security release, and the flaw it fixes is already being exploited in the wild. The uncomfortable part is that on an unpatched site, the update itself is the attack window: WordPress unpacks the new files into a folder inside your web root and finishes installing them while still running the old, vulnerable code. Two habits close that window — take a backup, and block web access to wp-content/upgrade before you click Update.

    The flaw is CVE-2026-119977, a time-of-check to time-of-use race in WordPress’s core upgrade process. It affects versions 4.7 through 7.1.3. WordPress shipped the fix on October 28, and CISA has since added the flaw to its known-exploited list after confirming attacks in the wild; Wordfence said it blocked 13,095 attempts against it in a single 24-hour stretch. This is not a theoretical hardening post.

    The short version

    • CVE-2026-119977 can give an unauthenticated attacker code execution during a core update. It is fixed in WordPress 7.1.4.
    • Versions 4.7 to 7.1.3 are affected, and the patch was backported to older branches, so check what you are actually running.
    • Blocking /wp-content/upgrade/ at the web server kills the attack without stopping the updater.
    • Order matters: back up, block, update, verify. Blocking after the update is too late for that update.
    • The plugin queue is bigger than the core queue — 549 plugin vulnerabilities were disclosed in a single week.

    What the flaw actually does

    When WordPress updates core with a full package, it downloads a complete copy of the new version and unpacks it into a temporary folder inside wp-content/upgrade/. That copy includes the installer. The folder lives under the web root, so the web server will happily serve files from it.

    The temporary folder used to have a name an attacker could predict or find. So the sequence is: you start the update, WordPress writes the new files into a reachable folder, and an attacker who is polling for that folder loads the installer and completes an install before WordPress deletes the copy. That is code execution with no account and no password. The 7.1.4 fix gives the temporary folder a random name, so there is nothing left to guess.

    Worth being honest about the shape of the risk: it is a narrow window and a high-complexity attack. It is not a drive-by. But narrow windows are exactly what automated scanners are built to hit, and “high complexity” stops meaning much when the scanning is free.

    Who is actually exposed

    • Sites on 4.7 to 7.1.3 that have not updated since October 28. That is the whole affected range, including old installs people forgot they own.
    • Shared hosting, most of all. When WordPress cannot write core files directly, it falls back to the full-package upgrade — the exact code path this bug lives in.
    • Anyone who turned off auto-updates and then stopped checking. Turning off auto-updates is a legitimate choice; turning off the calendar reminder that goes with it is not.

    If you are already on 7.1.4 or newer, the flaw does not apply to you, but the habit still does. If your host patches for you, ask for the core version number rather than for reassurance — the answer is a version, not a feeling.

    The five-minute update order

    Do these in order. The block has to exist before the update runs, not after.

    1. Back up the database and the files. A database export plus a tarball of the site directory. If you have never restored one, test the restore before you need it.
    2. Block the upgrade folder at the web server. On nginx, add a location block that returns 403 for ^~ /wp-content/upgrade/. On Apache or LiteSpeed, add a rewrite rule in the site’s root .htaccess. This only refuses HTTP requests; PHP can still write to the folder, so the updater keeps working.
    3. Update core. Dashboard → Updates → Update now, or from the command line with WP-CLI.
    4. Verify. Confirm the version, verify core checksums, then load the homepage and the login page while logged out.
    5. Leave the block in place. It costs nothing and closes the same trick in the next security release.
    # 1. Back up before anything
    wp db export ~/backup-$(date +%F).sql
    tar czf ~/files-$(date +%F).tar.gz public_html
    
    # 2. Block the upgrade folder (nginx), then reload nginx
    #    location ^~ /wp-content/upgrade/ { return 403; }
    #
    #    Apache / LiteSpeed: put this above the WordPress block
    #    in the site root .htaccess:
    #    RewriteRule ^wp-content/upgrade/ - [F,L]
    
    # 3. Update and verify
    wp core update
    wp core version
    wp core verify-checksums
    
    # 4. Plugin queue
    wp plugin list --update=available
    wp plugin update --all

    If a host control panel runs updates for you and something breaks while the folder is blocked, remove the rule, run the update, and put it straight back. What you do not want is an update that starts, fails halfway, and leaves a half-unpacked copy sitting in a web-reachable directory while you go looking for the cause.

    The plugin queue is bigger than the core queue

    Wordfence’s weekly report for October 26 to November 1 counted 549 vulnerabilities across 351 plugins and 4 themes, including 17 critical and 132 high-severity disclosures. Forty-one had no patch at the time of disclosure. You cannot update your way out of those — the only move is to deactivate and delete.

    A separate batch documented on November 9 covers 13 plugins, and four of them are common enough to check by name:

    Plugin Installs Problem Action
    LatePoint 100,000 Auth bypass, CVSS 9.1, through 5.7.6 Update now
    TranslatePress 400,000 Two stored XSS via comments, CVSS 7.2, through 3.3.8 Update now
    Amelia 90,000 SQL injection plus missing authorisation, CVSS 7.5, through 9.9.1 Update now
    wpForo 20,000 SQL injection and subscriber-to-admin escalation, CVSS 7.5, through 3.2.2 Update now

    None of these plug straight into core, so they are not today’s headline. They are the reason “I updated WordPress” is not the same statement as “I patched my site”. A booking plugin with an authentication bypass is a worse day than a narrow core race condition, and it does not announce itself.

    How to tell whether someone hit you during an update

    Three checks, five minutes, no plugins required:

    • Access logs. Search for requests to /wp-content/upgrade/, especially POSTs to an installer file from an address that is not yours. Reading access logs is the same skill as spotting a brute-force run, pointed at a different string.
    • Administrator accounts. List the administrators and compare the registration dates against the day you ran the update. A new admin you did not create is the whole answer.
    • Unexpected files. Anything ending in .php inside uploads, mu-plugins, or a leftover upgrade folder that you did not put there. This is part of the five-minute infection check.

    If two of the three come back dirty, stop tidying and treat the site as compromised. Cleaning the symptom without finding the entry point is how a site gets reinfected a week later; the recovery order is: isolate, preserve evidence, rebuild from a known-good backup, then rotate every credential that touched the site.

    The checklist I actually run

    • Core version is 7.1.4 or newer on every site I am responsible for, including the one I forgot about in September.
    • /wp-content/upgrade/ returns 403 from the public internet.
    • A backup from the last seven days exists, and one restore has been tested this year.
    • No active plugin has a published, unpatched vulnerability — those get deleted, not “monitored”.
    • Access logs checked for upgrade-folder requests in the last 30 days.
    • One admin account per person who actually needs it, and nobody’s old developer still on the list.

    None of this is exciting, which is the point. Sites rarely get hurt because the owner lacked a clever security stack. They get hurt because a three-week-old security release looked like a maintenance chore, and the update window stayed open while nobody was watching.

  • How to Fact-Check AI Drafts Before Publishing

    How to Fact-Check AI Drafts Before Publishing

    A benchmark reported yesterday by Alibaba researchers should be pinned above every content calendar. They collected 37,463 real requests from 26,314 coding sessions where people had struggled to get an AI assistant to finish a job, refined them into 273 tasks with 2,331 hidden acceptance criteria, and ran ten leading models through them. The best model, OpenAI’s GPT-6 Astra, met an average of 69.9% of the criteria per task. It fully solved 26.4% of the tasks. On multi-file repository edits, that figure fell to 17.9%.

    Coding is not blogging. But the shape of the failure is the same: the draft looks finished, most of it is right, and the last quarter is where the damage lives. A wrong version number, an invented quote, a link that goes nowhere, a confident sentence about something the model cannot know.

    So I do not publish an AI draft until it has been through a review pass that treats every factual sentence as a claim to be checked. Not a vibe check. A source check. Here is the exact pass I use.

    The short version

    • Assume every number, name, date and quote is wrong until you have opened a source.
    • Check the link, not just the claim. A dead or mismatched link is a factual error with a URL attached.
    • Delete any first-person experience the draft invented, and replace it with one you actually had.
    • Soften what you cannot verify, and say so plainly instead of sounding certain.
    • Read the finished piece aloud once. Confident nonsense survives silent reading and dies at speaking speed.

    Why AI drafts fail in the same five places

    After enough drafts, the errors stop being random. They cluster in five predictable places.

    1. Numbers without a source. A percentage appears with three decimal places and no origin. It may be real, remembered from somewhere, or invented to make the sentence sound researched. You cannot tell by looking, which is the point.
    2. Citations that do not exist. Titles, authors, journals and years that read plausibly. Ask for the URL and you get a 404, or a page that says something else entirely.
    3. Stale product facts. Pricing, limits, version numbers and feature lists change faster than training data. A sentence can be true in the model’s memory and false on the vendor’s site today.
    4. Borrowed experience. “When I tested this last month…” from a model that has never opened the tool. This is the most damaging one, because readers forgive a wrong number far more easily than a fake story.
    5. Confident predictions. “This will kill X” is an opinion wearing a fact’s clothes. It needs to be labelled as a bet, not delivered as a finding.

    Google’s documentation update this month makes the same point from the other side: it now tells publishers to fact-check and review AI-generated content before publishing, and warns against manufactured authorship. The compliance bar is no longer “did a human touch it?” It is “can you stand behind what it says?”

    The five-minute pass

    Five minutes is not enough for a 3,000-word research piece. It is enough for a 1,200-word blog post if you work in the right order. I keep the draft on the left and a browser window on the right, and I do not edit prose until the facts are settled.

    1. Mark the claims. Read the draft once and highlight every proper noun, number, date, quote and superlative. Do not fix anything yet. You are making a list, not editing.
    2. Open the primary source. Not a blog quoting a blog. For a study, the paper. For a product claim, the vendor’s own documentation or changelog. For a quote, the transcript or the recording. If the best you can find is a summary of a summary, the claim is not verified, it is repeated.
    3. Click every link. Check that it loads, that the title matches what you said it says, and that the date is current. A link that resolves to the right domain but the wrong page is worse than no link, because it looks like evidence.
    4. Rewrite from the facts, not from the draft. Once a claim is confirmed, write the sentence yourself using the source in front of you. This one habit removes most accidental plagiarism and most of the model’s smug tone at the same time.
    5. Read it aloud. Slowly. The sentence you skated past silently is usually the one that says nothing, or says something you cannot defend.

    A claims table you can keep

    When a piece has more than a handful of facts, I keep a small table beside the draft. It turns “I think this is right” into a yes or no. Mine has four columns, and the last one is the only one that matters.

    Claim type Looks like Check against If unverified
    Number “42% of sites…” The original study or dataset Cut the number or say “many”
    Quote “…” — named person Transcript, video, original interview Paraphrase the idea, not the words
    Product fact “Version 7.2 supports…” Vendor changelog or docs Say “check current docs” or cut it
    Personal story “When I tested this…” Your own notes or screenshots Cut it or run the test for real
    Link “Source: example.com” Open it; read title and date Delete the link and the claim

    The prompt I add before the first draft

    Most cleanup can be avoided by asking the model to expose its uncertainty instead of smoothing it over. I paste this after the brief, before any drafting starts. It does not replace the check. It makes the check shorter.

    Before you write, list the five factual claims this article will depend on. For each one, give the primary source you would use and mark it certain, uncertain, or unknown. Do not invent sources to fill the table.
    
    Then write the draft, and put [VERIFY] after every number, name, date, quote or product detail you are not certain about. Do not round over gaps.
    
    At the end, list the three sentences most likely to be wrong, and why.

    Two things to note. First, the model will still miss claims, especially ones it believes are common knowledge. The [VERIFY] tags are a floor, not a ceiling. Second, a source URL from a model is a lead, not proof. Open it. Read it. If it does not say what the draft says it says, the draft is wrong.

    What “verified” means before I press publish

    My standard is one question: if a reader emailed “where did you get this?”, could I answer in one sentence with a link or a specific experience? If the answer is a shrug, the sentence is not ready.

    That standard changes how the piece is written. A fuzzy number becomes “roughly” or disappears. A prediction becomes “I think” or gets cut. A product detail gets a date stamp: “as of November 2026” tells the reader when the fact was true, which is often more honest than pretending it is permanent.

    It also keeps the publishing workflow boring, which is what you want. The 90-minute post workflow leaves a real slot for this pass instead of pretending the draft is the finish line. The monthly planning afternoon is where the topics come from, so the writing day starts with decisions already made. And repurposing happens only after the facts survive, because a wrong number copied into five formats is five times the problem.

    Pre-publish checklist

    • Every number has a source open in a tab, or it is gone.
    • Every quote is verbatim from a primary source, with the speaker named correctly.
    • Every link loads and matches the claim it supports.
    • No first-person experience that I did not actually have.
    • No prediction stated as a fact; bets are labelled as bets.
    • Product names, versions and prices checked today, with a date stamp.
    • The whole piece read aloud once, start to finish.

    Next time you have a draft that feels finished, set a timer for five minutes and try to disprove it. Open the first number. Click the first link. Read the first quote. If everything survives, publish with confidence. If something does not, you just caught the 74% before your readers did.

  • How to Plan a Month of Content in One Afternoon

    How to Plan a Month of Content in One Afternoon

    Planning a month of content does not take a month. It takes one afternoon, and most of that afternoon is not writing.

    My version produces four titles, four skeletons, four hero images and a calendar with two dates per post. I do it in one block of roughly four hours on a Sunday, and then I stop thinking about content strategy until the next month. The writing still happens on weekdays, in 90-minute blocks, the way I described in how to write a blog post in 90 minutes.

    Planning and writing use different parts of your head. Planning is judgement: what is worth writing, in what order, with what promise. Writing is focus: one section at a time, no decisions. Mixed together, I make bad decisions and shallow drafts in the same hour.

    The short version

    • Block four hours once a month and do not spread planning across the week.
    • Build the topic list from questions you have already answered before inventing new ideas.
    • Leave the afternoon with four titles, four skeletons and four images — not twelve half-drafts.
    • Put two dates on every post: publish day and repurpose day. One date means the post dies after a week.
    • Keep one slot empty for the story that turns up mid-month.

    What the afternoon actually produces

    Not articles. The output of a planning afternoon is a folder of decisions that a tired version of you can execute later without thinking.

    At the end of four hours I want these in one place: four titles I would defend, four skeletons with headings and the asset each one needs, four hero images at final size, and eight calendar entries. Nothing else. If I end the afternoon with two finished drafts and no plan, I have solved this week and borrowed against next week.

    The plan has to survive a bad Tuesday. A skeleton promises my future self that the thinking is done; a half-written draft still forces me to decide what the second half argues.

    Hour one: mine the topic bank you already own

    Most people start this hour by brainstorming, which is the slowest method available. I start with four sources that working already generated:

    1. Search Console queries where I rank between roughly position 5 and 20. Those pages Google already understands but has not promoted, so a more precisely aimed post has a reason to exist. Search Console is the only free tool that tells me what strangers typed to find me; the setup steps are in how to set up Google Search Console properly.

    2. Questions I answered twice in email or chat. The rule is mechanical: if I typed the same explanation twice, it becomes a topic. I keep a plain text file called questions.md and paste the question in as it arrives, not the answer.

    3. Comments and replies that pushed back. Disagreement is a better topic source than praise, because the person is telling me exactly which part of my argument is unclear.

    4. Posts that got replies but no clicks. Usually the title promised one thing and the article delivered another. The fix is a rewrite or a follow-up that answers the question the title implied.

    At the end of the hour I want 8 to 12 candidate lines, each a single sentence that starts with the reader’s problem, not my subject. “Why does my WordPress site show a 500 after I edited functions.php” is a topic. “PHP error handling” is a category.

    One filter: if a topic needs numbers I do not have — revenue, traffic, client results — it goes to a parked list until I actually have them.

    Hour two: four titles and four skeletons, no drafts

    Four is not magic: it is roughly how many posts I can hold in my head, and it leaves room for the unplanned post that always appears. Twelve planned posts become debt.

    I write each title first, because the title is the promise the article has to keep. Two shapes do most of the work:

    • How to X without Y — the reader gets a method and a constraint at the same time.
    • What X actually means (and what to do about it) — good for error messages, policies and anything where the reader is confused before they are curious.

    Then the skeleton: five lines maximum — three to five headings plus the one concrete asset the post needs. I write it in a plain file:

    title: How to X without Y
    promise: reader can do X by the end, with Y still in place
    asset: one table comparing the options
    h2: What X actually needs
    h2: The three steps
    h2: Where it breaks
    h2: Checklist
    links: /write-a-blog-post-in-90-minutes/, /google-search-console-setup/
    repurpose: newsletter + 2 short videos

    Six minutes each, and the article has a shape before I start writing: at 7am on a Wednesday I can read six lines and type, instead of deciding what the piece is about.

    Hour three: batch the parts that do not need your brain

    Hero images, slugs, excerpts, categories and internal links are small decisions that interrupt writing one at a time, so I make them all at once at planning time.

    I generate the four hero images from final titles with my own script, so the card text is already correct and alt text gets written in the same minute, while I remember what the article argues. Slugs and excerpts go into the same file as the skeleton.

    The internal links are the part people skip, and it is the part that compounds. Every planned post gets two links back to older articles and one forward link to a post scheduled later in the month. That forward link only exists if you plan a month at a time, and it keeps the archive getting crawled instead of going quiet after week two.

    Hour four: two dates per post, not one

    Every post gets a publish date and a repurpose date two or three days later. The second date is not optional. Publishing without a repurpose slot is how a good article reaches forty people and disappears.

    The repurpose block is about 45 minutes and it is mechanical: pull the promise for the newsletter, the headings for the video beats, the steps for a forum answer, and one line for social. I wrote the full set of cuts in my content repurposing workflow, and none of it requires a new idea.

    Hour Output Done when
    1 8–12 candidate topics at least four you would actually write
    2 Four titles, four skeletons each has headings and one named asset
    3 Images, slugs, excerpts, link map files exist with final titles and alt text
    4 Eight calendar entries publish and repurpose dates both set

    Where this plan falls apart

    Four failure modes I have hit:

    Planning too many posts. Twelve planned posts feel productive and produce nothing, because each one is a decision I have to re-open. Four keeps a weekly rhythm with slack.

    Planning posts that need data. A topic like “what my first month of hosting actually earned” cannot be planned before the month exists. If a skeleton has a number I cannot fill in today, it is parked.

    Skipping the repurpose date. The article ships, the newsletter does not, and the month looks busy while the audience stays flat.

    Treating the plan as a contract. If a story lands that fits the blog, it takes the empty slot, not a fifth squeezed in on top. The plan removes decisions; it does not forbid judgement.

    The test of a good afternoon

    If I can open the folder on a Wednesday morning, read one skeleton, and be typing within two minutes without deciding anything, the afternoon worked. If I open the folder and start reorganising the list, it did not — that is planning as procrastination.

    The checklist I actually run before closing the file:

    • Four titles, each a promise a reader can repeat out loud.
    • Four skeletons with three to five headings and one named asset.
    • Four hero images generated from the final titles, with alt text.
    • Eight dates: four publishes, four repurposes.
    • One empty slot left deliberately blank.

    Block the next four hours before you close this tab. The month is cheaper to plan than to improvise, and improvising is what makes publishing feel like a second job.

  • My Content Repurposing Workflow: One Post, Five Formats

    My Content Repurposing Workflow: One Post, Five Formats

    One blog post should leave the house as five pieces of content, and the trick is that you never write any of them from scratch. After the article is published I spend about 45 minutes cutting the same material into a newsletter, a short video script, a social post, and a forum answer. Each format pulls from a different layer of the post, which is why the words come out fast and still sound like they were written for that place.

    I covered the writing half of this in How to Write a Blog Post in 90 Minutes. This is the second half: what happens to the file after I hit publish.

    The short version

    • Treat the finished post as raw material, not a monument. Every other format is a cut of it.
    • Each cut takes a different layer: the promise, the skeleton, the proof, or one quotable sentence.
    • Give each format one job. Newsletter for relationship, video for reach, social for distribution, forum for credibility.
    • Do all four cuts in one sitting, while the thinking is still in your head.
    • Five formats is a ceiling, not a quota. Skipping one is a decision, not a failure.

    Start from layers, not from a blank page

    Most repurposing fails because people open a new document and try to write a post about the article. That is a rewrite with extra steps. The faster path is to accept that the post contains everything you need, just packed for one reader in one mood.

    Before I cut anything, I break the article into four layers:

    • The promise — the sentence in the intro that says what the reader gets. It becomes the newsletter subject line and the first line of the social post.
    • The skeleton — the H2 headings. They become the video beats, because they are already an ordered argument.
    • The proof — the steps, commands, settings, or examples. They become the forum answer.
    • The quotable line — the one sentence I would underline. It becomes the opening line of the video and the hook of the social post.

    Once those four things exist, repurposing is selection, not creation. That is the whole trick.

    The five cuts, in order

    This is the shape I work to. The times are what the cutting takes me once the article is live, not the time to write the article.

    Format Cut from Time Job
    Blog post (source) Outline and draft 90 min The asset you own and can rank
    Newsletter Promise and skeleton 10 min Relationship with people who opted in
    Short video script Skeleton and proof 10 min Reach on a platform you do not own
    Social post Quotable line and link 5 min Distribution to people who will never subscribe
    Forum answer Proof section 15 min Credibility where the question is already being asked

    The newsletter: send the promise, not the post

    The mistake here is pasting the whole intro and calling it a newsletter. People subscribed to hear from you, not to get a second copy of a page they can read for free. I send 150 to 250 words: the promise, three bullets from the skeleton, and one link. If I cannot explain the value in three bullets, the article probably has more than one idea in it.

    Subject: [the promise, rewritten as a question or a number]
    
    One sentence on why this matters this week.
    
    - The problem, in the reader's words
    - The step most people skip
    - What to do first
    
    Full post: [link]
    
    That is the whole email. No preamble, no signature block longer than one line.

    Two rules: the subject line describes what is inside, and the bullets are useful on their own. A newsletter that only works if you click is an ad.

    The video script: your headings are already the beats

    A 30 to 45 second script does not need a scriptwriter. It needs three beats, and the article already has them. I take the problem from the intro, the method from the first two or three H2s, and the proof from whatever concrete example the post contains. Then I write it as spoken lines, one idea per line, eight to ten words each.

    The shape that works for me:

    • Beat 1, problem (0–5s): the viewer’s own complaint, said plainly. No greeting, no “in this video”.
    • Beat 2, method (5–25s): the two or three steps, in order, with the reason attached to each one.
    • Beat 3, proof (25–35s): the specific result from the post — the log line, the setting, the thing that changed.
    • Close (35–40s): one action, one place to go.

    If a beat needs a diagram to make sense, it belongs in the post.

    The social post: lift one paragraph and stop

    Social is not a summary. A summary of a long article is boring in every format. What travels is one opinionated sentence, stated without hedging, followed by enough context to be useful and a link for the people who want the rest.

    My routine: pick the most contrary sentence in the article, paste it first, delete every word that softens it, then add one line of value and the link. On X the limit is 272 weighted characters and a link counts as 23, so the real text budget is around 250. No hashtags there; they read as spam. On LinkedIn or Facebook I add a line break before the link so the post does not end in a wall of URL.

    What I never do is announce that I wrote a blog post. Nobody owes me a click because I was productive. The sentence has to earn it.

    The forum answer: answer first, link second

    Forums are where the same question gets asked every week, and the easiest place to be useful if you resist advertising. I find a thread where I actually know the answer, then write the answer as if my site did not exist. Only at the end, if there is more detail than a forum reply should carry, do I mention that I wrote it up in full.

    Practical version: lead with the direct answer in the first two lines, give the steps in the order you would do them, and name the one thing that usually goes wrong. Strip the brand voice completely. A forum reader can smell a marketing line in the first sentence, and then the answer is dead no matter how correct it is.

    What I skip, and why five is a ceiling

    Not every post deserves five cuts. Some topics are useful and quiet; they earn a newsletter mention and nothing else. Others are already saturated on video and do better as a written reference. I decide based on where the question lives, not on a content calendar that says every post gets five assets.

    Things I skip by default: Instagram carousels, because the design time eats the writing time; podcast appearances, because they are a different skill; and old forum threads. Things I only do when the post is genuinely strong: a second video, a longer LinkedIn version, and a follow-up article that goes deeper on one section.

    Two weeks after publishing, I check which pieces earned impressions and which earned nothing, then put the next month’s effort where the numbers actually moved. That is a Google Search Console habit, and I wrote the setup for it in How to Set Up Google Search Console Properly. Repurposing without that check is just making more things.

    The 45-minute checklist

    1. Copy the promise sentence and the H2 list into one scratch file.
    2. Write the newsletter from those two things only. No new ideas.
    3. Turn the H2 list into three video beats and one closing action.
    4. Lift one paragraph for social, cut it until it fits, add the link last.
    5. Find one real question and answer it with the proof section, brand voice off.
    6. Schedule all four and log them in a single row of a spreadsheet: date, post, formats, links.
    7. Set a reminder for two weeks out to check impressions before deciding what to repeat.

    The post is not finished when it is published. It is finished when the same idea has met five different readers in the five places they already spend time, and none of them had to be told it was the same idea.

  • How to Write a Blog Post in 90 Minutes

    How to Write a Blog Post in 90 Minutes

    A blog post takes me 90 minutes from blank page to published. The trick is not typing speed — it is doing one job at a time instead of all four at once. Research, outline, draft and edit each need a different kind of attention, and every switch between them costs you the thread you were holding. Ninety minutes is enough for a 900 to 1,200 word post that answers one question properly. It is not enough for a guide that tries to answer ten.

    The short version

    • Five blocks: 15 minutes research, 10 outline, 35 draft, 20 edit, 10 publish.
    • Research ends when the timer ends — three sources, then start writing.
    • The outline is a list of questions a reader would ask, not a list of headings.
    • The first draft is allowed to be bad. Fixing it is a separate job done later.
    • Editing is three passes in a fixed order: structure, sentences, facts.

    The five blocks, with the one rule that keeps each one from eating the next:

    Block Minutes The job The rule
    0:00 – 0:15 15 Collect what one question needs Three sources, then stop
    0:15 – 0:25 10 Turn the question into sub-questions If you cannot answer it in one line, cut it
    0:25 – 1:00 35 Write the whole thing, badly No editing, no new research
    1:00 – 1:20 20 Structure, sentences, facts Three passes, in that order
    1:20 – 1:30 10 Image, links, slug, live check Done beats perfect

    Minutes 0–15: research with a stop rule

    Open a scratch document and write the post’s question the way a reader would type it into a search box. Not “content marketing tips” — “how long should a blog post be before people stop reading”. One question. If you cannot get it down to one, you are about to write two posts badly instead of one post well.

    Then collect three things and nothing else:

    • What you already know from doing the work. For a migration post, that is the step order and the one step you always forget.
    • One authoritative source — the official documentation, the changelog, the pricing page. Copy the exact sentence and the URL into your scratch document so you never have to find it twice.
    • One real example: a screenshot you took, a command output, a before-and-after you can describe from memory.

    If a fact is missing when the timer rings, do not go looking for it. Type a placeholder — [CHECK VERSION] — and keep moving; you will verify it in the edit block or cut the sentence. Research is where time disappears, because every tab feels useful and usually is not. The post needs three things, and you already have two.

    Minutes 15–25: turn the outline into questions

    Most outlines are a list of nouns: “Introduction, Why it matters, Best practices, Conclusion.” That produces a post that says nothing, because “Best practices” does not tell you what to write underneath it. Replace each heading with the question a reader would actually ask, then write the one-line answer. If there is no one-line answer, you have found the section that will take an hour — cut it or split it into its own post.

    This is the outline I type, every time:

    TITLE (the reader's question, in their words)
    
    1. What do I need before I start?
       -> one line:
    
    2. What does it look like when it is done?
       -> one line:
    
    3. What goes wrong, and what do I do about it?
       -> one line:
    
    4. What is the checklist?
       -> one line:
    
    STILL OWED: [ ] screenshot  [ ] version number  [ ] link

    Four questions is a 900-word post; six is about 1,200. More than that is a series, and it will read like a list of everything you know instead of an answer to the title.

    Minutes 25–60: draft straight through

    The draft block has one rule: do not stop to fix anything. Not the sentence you just wrote, not the fact you are unsure of, not the comma you can feel is wrong. Every stop is a context switch, and the switch back costs more than the fix saved.

    Practical version of that rule:

    • Write the opening as three blunt sentences that answer the question, then leave it alone until the edit block. Do not polish it now, and do not write a warm-up paragraph first.
    • Fill in each outline question with paragraphs. When a paragraph is not working, type the sentence you are trying to say in capitals and move to the next one.
    • No new research. If you need a fact you did not collect, write a placeholder.
    • No formatting, no image hunting, no plugin fiddling. Those belong to the publish block.

    A thousand words in 35 minutes is under thirty words a minute — slower than you type. The time goes into deciding what the next sentence says, which is the actual work.

    Minutes 60–80: edit in three passes

    One long read-through finds typos and nothing else, because you cannot judge structure and commas at the same time. Three short passes, in this order:

    Pass one, structure. Read only the subheadings and the first sentence under each one. Does the post answer the question in the title? Is any section saying the same thing as the one above it? Cut it, even if you like it. Move the strongest section to the top.

    Pass two, sentences. Read each paragraph once, out loud in your head. Split anything over about thirty words. Delete the phrases that only exist to fill space — “in today’s fast-paced world”, “it is important to note that”, “let’s dive in”.

    Pass three, facts. Every number, date, product name and version gets checked against the source you saved, or deleted. This is where credibility is decided, and it is the pass people skip when they are running late. If you cannot verify a claim, say plainly that you have not tested it. A shorter post with true sentences is worth more than a long one with one invented figure.

    Minutes 80–90: publish, then stop

    The publish block is mechanical, so it gets ten minutes:

    • Slug: the question, shortened, words separated by hyphens. Set it before you publish — I cover the permalink settings in the WordPress settings I change on every new site.
    • Image: resize it before upload, and write alt text that describes what is in the picture rather than repeating the title. Oversized uploads are one of the reasons WordPress sites end up slow.
    • Internal links: two or three, each pointing at a post that answers the reader’s next question. Click every one before publishing.
    • Excerpt or meta description: two sentences that answer the question again, in different words from the title.
    • Check: open the live URL. Does it load, does the title match, does the image appear, does the page fit a phone screen?

    Then stop. The temptation is to keep tweaking, and that is how a 90-minute post becomes an afternoon. Tomorrow’s post is the next thing, not this one.

    Where the 90 minutes actually goes wrong

    Four failures account for almost all of it. Research expands to fill the time available, so set an alarm. Editing starts during the draft, so the draft never gets finished. The outline is written as nouns instead of questions, so the writing block has nothing to aim at. And the post tries to be comprehensive, when comprehensive is three posts and the reader came for one answer.

    One honest exception: a post that depends on data you must gather — dashboard screenshots, a test to run, numbers to collect — is not a 90-minute post. Give it three hours or split it; forcing it into the block means publishing half the evidence.

    Start smaller than it sounds: take the question people ask you most, set a timer for 15 minutes, and do only the research block. When it rings, write the four questions and stop if you want. And if you are choosing what to write next, the fastest source of real questions is the Performance report in Google Search Console: the queries your pages already appear for are the questions you are closest to answering well.

  • How to Set Up Google Search Console Properly

    How to Set Up Google Search Console Properly

    Search Console is the only place Google tells you what it actually did with your site — which pages it indexed, which queries showed you, and whether a human reviewer took action against you. The setup that matters is short: one Domain property verified by DNS, one sitemap submitted, alerts going to an address you read, and a ten-minute check once a week. Everything else in the interface can wait until you have traffic worth analysing.

    The short version

    • Create a Domain property, not a URL-prefix property. One property covers http, https, www and every subdomain you own.
    • Verify with a DNS TXT record and leave the record in place. Google re-checks it, and deleting it costs you access.
    • Submit the sitemap WordPress actually generates — /wp-sitemap.xml on a stock install, /sitemap_index.xml with Yoast or Rank Math.
    • Three reports are worth opening: Performance, Page Indexing, and Security & Manual Actions.
    • Data lags a couple of days and new pages take weeks to be crawled. Check weekly, not hourly.

    Pick the Domain property, not the URL prefix

    Google offers two kinds of property. A URL-prefix property is tied to exactly one protocol, one hostname and one path: https://example.com does not include https://www.example.com, and neither includes http://. A Domain property covers all of them, plus every subdomain, because ownership is proved at the DNS level for the bare domain.

    Property type Covers Choose it when
    Domain Every protocol and subdomain: example.com, www., shop., http and https You control the DNS, which is the normal case for a site you own
    URL prefix One scheme, host and path, exactly as typed You cannot edit DNS, or you are monitoring a section of a domain you do not own

    In the add-property screen, choose Domain, type the bare domain without https:// and without a trailing slash, and continue. Do not also create a URL-prefix property “just in case”: the two keep separate data and separate emails, so you end up watching two half-empty dashboards instead of one complete one.

    If your DNS lives at Cloudflare, the record goes in the same place as everything else — the Cloudflare setup walkthrough covers the settings that break sites.

    Verify with a DNS record and leave it alone

    Google gives you a TXT value that looks like this. Copy it exactly, including the google-site-verification= prefix.

    Type:  TXT
    Name:  @            (the bare domain)
    Value: google-site-verification=AbC123dEf456...
    TTL:   Auto or 3600

    In cPanel that is Zone Editor → Manage → Add Record → TXT. In Cloudflare it is DNS → Records → Add record, type TXT, name @. Save, wait a few minutes, then press Verify back in Search Console. When verification fails it is almost always one of three things: the record went on www instead of the root, the value was pasted with a smart quote or a line break from an email, or the DNS change has not propagated yet. Give it an hour before you start changing things.

    The mistake I see most often is cleanup. Six months later someone tidies the DNS zone, deletes the “random TXT record”, and the property breaks. Google re-checks ownership periodically; when the record is gone, access goes with it. If you move nameservers, recreate the TXT record at the new provider before you flip the delegation.

    The HTML-tag and Google Analytics verification methods are fine, but both are fragile in WordPress: a theme change can strip the tag from the header, and a plugin that removes the analytics snippet takes the property with it. DNS survives theme updates, plugin audits and migrations, which is why it is the one I use.

    Submit the sitemap WordPress already builds

    You do not need a sitemap plugin to start. Since WordPress 5.5 there is a built-in sitemap at /wp-sitemap.xml; if you run Yoast or Rank Math it is /sitemap_index.xml instead. Open the URL in a browser first — you should see XML, not a 404 and not a redirect to the homepage.

    Then go to Indexing → Sitemaps, paste the full URL, and submit. The status column should read Success within a day or two. Couldn’t fetch usually means the URL redirects, robots.txt blocks it, or the response is HTML; fix that before submitting again, because repeated failures do not speed anything up.

    Two more things worth doing once: add a Sitemap: line to robots.txt pointing at the same file, and open the sitemap index itself. A stock WordPress sitemap splits into posts, pages, categories and users, and if one of those child sitemaps errors the parent reports it as a warning. A sitemap is a hint, not a command: it tells Google where the URLs are and nothing more. Submitting the same file every day does not push a page into the index.

    Read three reports and ignore the rest

    Performance is the report people open first and the one most often misread. It shows queries, pages, countries and devices for a chosen date range, three months by default. Two cautions: the data lags by a couple of days, so yesterday always looks like a cliff, and “average position” is an average across every impression, not the position you see when you search from your own desk. Filter by page to see which queries actually feed one article.

    Page Indexing is the honest one. It lists every URL Google knows about and why it is or is not indexed. The reasons are not equally urgent:

    • Discovered – currently not indexed: Google knows the URL exists and has not crawled it yet. On a small site this is often just queue time. Internal links from pages that are already indexed help more than anything else.
    • Crawled – currently not indexed: Google fetched the page and chose not to keep it. That is usually thin or near-duplicate content. Improve the page instead of requesting indexing every morning.
    • Excluded by noindex: a setting, not a bug. Check the page source for the tag, then find the plugin or theme option that adds it.
    • Alternate page with proper canonical tag: normal on paginated and tagged archives. Not an error.
    • Duplicate, Google chose a different canonical: look at your canonical tags; on WordPress the usual culprits are archives and query strings.

    Security & Manual Actions holds two sections that look similar and mean different things. A manual action is a person at Google deciding your site breaks the spam policies; a security issue is Google believing your site is hacked or serving malware. Both are emailed to verified owners and both are urgent. If either list is not empty, stop reading about search optimisation and deal with it — the five-minute infection check is the fastest first pass, and the recovery walkthrough is the order I would work in.

    The weekly ten-minute check

    Search Console emails you about manual actions and security issues. It will not email you when a page quietly drops out of the index, so put a recurring slot in the calendar and work through the same five items:

    1. Page Indexing — did the “not indexed” count jump by more than a few pages? Open the reasons and look for a new one.
    2. Performance — compare the last 28 days with the previous 28. Find one query or page that lost clicks and write down why.
    3. Security & Manual Actions — confirm both sections are empty.
    4. Sitemaps — status Success, with a recent “last read” date.
    5. Settings → Users and permissions — once a quarter is enough; remove accounts you no longer control.

    That is the whole routine. The reason to keep it boring is that the signals that matter — a page dropping out of the index, a query collapsing, a manual action — are visible within a week if you look, and invisible for months if you do not.

    If you only do one thing this week: create the Domain property, paste the TXT record at your DNS provider, submit /wp-sitemap.xml, and put the ten-minute check on your calendar. Then go back to writing. Search Console rewards patience far more than it rewards fiddling, and once the property is verified and the sitemap is in, the only thing left to improve is the site itself.

  • Email Deliverability Basics for Small Sites: SPF, DKIM, DMARC

    Email Deliverability Basics for Small Sites: SPF, DKIM, DMARC

    Three DNS records decide whether your email reaches the inbox: SPF lists the servers allowed to send as your domain, DKIM signs every message so the receiver can tell it was not altered on the way, and DMARC tells the receiver what to do when either check fails. Get one wrong and small sites do not see an error — invoices, password resets and replies just land in spam or vanish. Here is the whole setup, in the order I do it.

    The short version

    • SPF is one TXT record per domain listing the servers allowed to send. Two SPF records is not redundancy, it is a broken domain.
    • DKIM needs a private key on the mail server and a public key in DNS at selector._domainkey.yourdomain. A DNS row without a signing server is decoration.
    • DMARC is a TXT record at _dmarc that ties the From address to SPF and DKIM and sets a policy. Start at p=none, read the reports, then tighten.
    • Test by sending to a real Gmail address and reading the headers, not a checker that only reads DNS.
    • If WordPress sends through PHP mail(), fix that before touching DNS. Hosting IPs are the ones receivers distrust.

    What each record actually does

    They fail separately. SPF reads the envelope sender — the Return-Path, which your customer never sees. DKIM reads a cryptographic signature over the headers and body. DMARC reads the From header your customer does see, and asks whether SPF or DKIM passed for that domain.

    Record Lives at What it proves Usual mistake
    SPF (TXT) @ Which servers may send as you Two SPF records, or over ten lookups
    DKIM (TXT) selector._domainkey The message was signed by your key Key in DNS, server never signs
    DMARC (TXT) _dmarc What to do on failure, and where reports go p=reject before anything was verified

    Where the records go, and what is live now

    The records belong in the DNS zone, where the A record lives. In cPanel that is Zone Editor; in Cloudflare it is DNS → Records → Add record, type TXT, TTL Auto. If your nameservers sit at the registrar while the site sits on hosting, the mail records still go at the registrar: email DNS follows the nameservers, not the web server. On Cloudflare, mail records must be DNS only, never proxied — the same rule as in the Cloudflare setup guide.

    Look at what is published before you change anything:

    # SPF
    dig +short TXT example.com | grep -i spf
    
    # DKIM — use the selector your provider gives you
    dig +short TXT default._domainkey.example.com
    
    # DMARC
    dig +short TXT _dmarc.example.com
    
    # Windows, no dig:
    nslookup -type=TXT example.com

    Two lines of v=spf1 means the record exists twice — worse than missing.

    SPF: one record, ten lookups, and the ending that matters

    An SPF record lists who may send, plus a qualifier for everyone else. For a site sending from its own hosting and one outside provider:

    v=spf1 a mx include:_spf.provider.example ip4:203.0.113.10 -all

    Read it left to right: a allows the server behind your A record, mx your incoming mail hosts, each include: another domain’s SPF list, ip4: a single address. Replace the example IP with yours.

    Two rules break more SPF records than anything else. First: one SPF record per domain name. If your host adds one and a marketing tool tells you to add another, merge them into a single line. Second: the ten-lookup limit in the SPF specification. Every include, a, mx, ptr and exists counts toward it, and going over returns a permanent error that receivers read as “no valid SPF”. MXToolbox and dmarcian both report the lookup count; check it before adding another include.

    End with -all once you are confident, ~all while you are still finding senders you forgot. Soft fail is a test mode; it protects nothing.

    DKIM: the key has to exist on the server

    DKIM is the record people copy correctly and still get wrong, because the DNS entry is only half of it. The public key goes in DNS; the private key stays on the machine that sends your mail, and that machine must be configured to sign. Publish the record without enabling signing and every message fails DKIM while you edit DNS for nothing.

    The right order: enable DKIM in your hosting panel first — in cPanel it is Email Deliverability — then copy the exact record it generates. Do not generate your own key pair and hope the panel picks it up. The selector is part of the record name and your provider chooses it; default, mail and google are common.

    If your DNS host splits a long TXT value into quoted chunks, that is normal; resolvers join them again. Breaking the value yourself is not.

    DMARC: monitor first, enforce later

    DMARC is the policy layer. Start with this:

    v=DMARC1; p=none; rua=mailto:dmarc@example.com; adkim=r; aspf=r; pct=100

    p=none means “deliver as normal, just tell me what happened”. It protects nothing by itself, and that is exactly why it is the right first step: your reports will show every legitimate sender you forgot, including the contact form on your own site. Point rua at a mailbox you actually read, or at a reporting service — the reports arrive as XML and nobody reads them by hand.

    After a few weeks of clean reports, move to p=quarantine and then p=reject, in steps with pct if you send volume. Alignment matters more than it looks: DMARC passes only if SPF or DKIM passes and the domain in that result matches the From domain. A host that sends as yourdomain.com but authenticates as its own domain passes SPF and still fails DMARC — the most common cause of “all my records are green but DMARC fails”.

    Test from a real mailbox, then fix the sending path

    Send a message from the site’s contact form and one from webmail to a Gmail address you own. Open it, use the three-dot menu, choose Show original, and read the authentication results near the top. You want three passes: SPF, DKIM with your selector, DMARC. Send the same message to an Outlook address too — Microsoft’s filters weigh reputation differently and will catch a problem Gmail forgives.

    If SPF passes and DKIM fails, the server is not signing or the selector does not match. If both pass and the mail still lands in spam, the problem is reputation, not authentication: a domain that has never sent mail looks exactly like a spammer’s first domain. Send consistently, keep volume modest at the start, and make sure every message is one somebody asked for.

    The WordPress part trips up more small sites than DNS does. PHP mail() sends from the web server’s IP, shared with every other site on that host — and that is the reputation your mail inherits. Configure SMTP to your real mailbox or a transactional provider (WP Mail SMTP and Post SMTP both do it), and check that outbound port 25 is not blocked, which many VPS providers do by default; port 587 exists for that reason. If the host’s IP reputation is the problem, no DNS record saves you, which is worth weighing when you compare cheap hosting against good hosting.

    Forwarding is the last trap: a forwarder re-sends with a new envelope sender, so SPF fails at the destination while DKIM usually survives. And when you change hosts, do not touch MX and SPF in the same hour — the migration checklist covers the sequencing so mail does not bounce.

    The checklist before I call it done

    • Exactly one SPF record, ending in -all, under ten lookups, covering every tool that sends for you.
    • DKIM signing enabled on the server, selector in DNS matching the signature, confirmed by a real message.
    • DMARC at _dmarc with p=none and a rua mailbox someone reads.
    • Test messages to Gmail and Outlook, all three checks passing in the headers.
    • WordPress sending over SMTP on port 587, not PHP mail().
    • Reverse DNS set for the sending IP if you run your own server.

    Do it in that order today: SPF, DKIM, DMARC at p=none, then one test message to your own Gmail and read the headers. It tells you more than any dashboard, because it measures what your customer receives.

  • How to Choose a Domain Name You Won’t Regret in 3 Years

    How to Choose a Domain Name You Won’t Regret in 3 Years

    The domain name is the only part of a website you cannot quietly swap later. Hosting, theme, even the platform can change in an afternoon. A domain carries every inbound link, every email address and every mention anyone ever wrote about you. Pick it the way you would pick a business name — slowly, and with year three in mind.

    My rule is simple. A name is safe to buy when it passes three tests: someone can hear it once and type it correctly, the renewal price does not jump after the first year, and no trademark or established business is already sitting on it. Everything else is taste.

    The short version

    • Say it out loud to a real person before you check availability. If they ask you to repeat it, the name is wrong.
    • Take the .com unless you are deliberately local. Local is a strategy, not a consolation prize.
    • Judge the price at renewal, not the first-year promo. That is where registrars earn.
    • Register the domain in your own account, with your own email, separate from your hosting.
    • Run four checks before you pay: trademark, past use, social handles, and who controls the DNS.

    The phone test comes before the availability check

    Most people open a registrar first and start typing names into a search box. That is backwards. The registrar will tell you what is available; it will never tell you what is usable.

    Read the name to someone over the phone, or send it in a voice note. If they ask you to spell it, or read it back wrong, you have your answer before you spend anything. A name that needs a sentence of explanation is a name you will explain for years.

    What I avoid, in the order it usually bites:

    • Doubled letters at the join. Two words that end and start with the same letter merge when spoken.
    • Numbers. “4” or “2” forces a digit-or-word decision on every visitor.
    • Hyphens. People drop them, so assume half your visitors type the unhyphenated version.
    • Plurals and possessives. If the name needs an apostrophe or a final “s”, people will leave it out.

    Two words, three syllables, no explanation. If you can say it in a noisy restaurant and get it typed back correctly, it survives a business card and a customer telling a friend.

    Brand or keyword: decide what the name has to do in three years

    Keyword domains describe what you sell. Something like klplumber24.com tells a stranger exactly what you do and roughly where. That is a real advantage for a local service business that will still be doing the same work later.

    The cost is that the name becomes a ceiling: add a second service, move city, or sell the business, and the name works against you.

    Brandable names — a short invented or semi-invented word — cost you clarity up front and repay it later. You can add services, change cities, launch products under the same roof. My compromise for most small sites: a short brandable root plus a plain-language tagline in the site title.

    One honest exception: if the business is local and service-specific with no ambition to change, the keyword name is the better marketing decision. Buy it knowing it is a permanent label.

    The extension is a trust decision, not a fashion decision

    Use .com when you can get it. Not because it ranks better, but because it is the default in people’s heads. When someone half-remembers your name, they type .com first.

    Country extensions — .com.my here in Malaysia, .co.uk, .com.au — buy local credibility. If your customers are all in one country and you compete with local businesses, that signal is worth the extra cost and the registration rules.

    New generic extensions are fine and often cheap, but they carry a spelling tax and a trust question. Some corporate mail filters treat unfamiliar extensions with suspicion. I use them for campaign sites, not for the address that goes on the invoice.

    The renewal price is the real price

    Registrars advertise the first year and earn on the rest. Before you check out, find the renewal price for the same domain and multiply it by three. If the promo year is cheap and the renewal is not, you have not found a bargain — you have found a discount on the least important year. It is the same trick as cheap hosting that looks fine until you read the fine print.

    Cost line What to check What it costs if you skip it
    Renewal price The price in year two, not the promo A bill you did not budget for, every year
    Transfer and redemption Cost to move out, cost to recover a lapse Redemption fees are punitive by design
    Privacy and email Is WHOIS privacy included or an upsell? Your home address in a public database

    The redemption line matters more than it looks. Let a domain expire and you do not simply re-buy it at the normal price — you pay a recovery fee, and sometimes you lose the name to an auction while you decide. Which is why auto-renew and a card on file are part of the purchase.

    Four checks before you pay

    This takes about twenty minutes and has saved me from names I would have regretted.

    1. Trademark. Search the national register — MyIPO in Malaysia — and a plain web search for the name plus your industry. You are looking for an existing business in the same field, not for any use of the words anywhere. A trademark dispute is decided by who registered first and in what class, not by who bought the domain first.

    2. Past life. Check the Wayback Machine and search the name. A domain that previously hosted spam, a pharmacy or a link farm carries that history. Search engines remember longer than registrars do, and cleaning it up is slow, uncertain work. A cheap expired domain with backlinks is usually cheap for a reason.

    3. Handles. Look up the same name on the social platforms you will actually use. A name that is free as a domain but taken everywhere else means every profile needs a variant, and you spend years explaining which one is you.

    4. DNS and ownership. Confirm you get full DNS control and that the registrant will be you. If a host or a developer offers to register it “for you”, decline politely. The account holder is the owner in every dispute that matters.

    whois yourname.com | grep -iE "registrar|creation|expiry"
    dig +short NS yourname.com
    dig +short MX yourname.com

    Read the output, not just the exit status. A creation date from years ago on a name you thought was free means it was dropped and re-registered. Name servers pointed somewhere unexpected mean it was in use recently.

    Register it for the person you will be in three years

    Buy in your own registrar account, using an email address on a different domain. If your only recovery address is the domain that just lapsed, you cannot receive the recovery message. Turn on two-factor authentication too — domain theft happens through registrar logins, not DNS attacks.

    Keep the domain and the hosting in separate accounts, ideally separate companies. When you move a site to a new host, the domain should stay exactly where it is and only the DNS records should change. I keep DNS at Cloudflare and treat the registrar as a billing relationship, so a migration is a nameserver edit rather than a transfer.

    Register for several years when the price is locked, and keep auto-renew on regardless. The renewal is a rounding error next to what losing the name would cost.

    Before you click buy

    • Say the name to one real person. Fix it if they hesitate.
    • Search the trademark register and the name itself.
    • Check the year-two renewal price and the transfer fee.
    • Check the social handles you will actually use.
    • Confirm the registrant will be your own account, with 2FA.
    • Set auto-renew and a calendar reminder thirty days before expiry.

    If you do only one thing from this article: check the renewal price before the first-year price. The name you keep for three years is the one you can afford to keep, and the best time to find that out is before you print it on anything.

  • Anatomy of a Hacked WordPress: What Attackers Leave Behind

    Anatomy of a Hacked WordPress: What Attackers Leave Behind

    When a WordPress site gets hacked, the visible symptom is usually the last thing to arrive. The real footprint is a handful of files, a couple of database rows, and one user account you did not create. If you know the six places to look, you can tell a hacked site from a broken one in about ten minutes — and you can do it without installing a security plugin.

    Clean up a few of these and the pattern repeats: the same four directories, the same three tables, the same two persistence tricks. Attackers reuse what already works.

    The short version

    • Most infections live in wp-content/plugins, wp-content/mu-plugins, wp-content/uploads, and the active theme’s functions.php.
    • mu-plugins is the favourite hiding place: files there load automatically and never appear in the Plugins screen.
    • Core files usually get one or two lines changed, so run wp core verify-checksums before you trust anything else on the site.
    • Check users and cron next. A rogue administrator or a scheduled event calling an outside URL is persistence, not just a symptom.
    • Cleaning files without finding the entry point means you will be back here in three weeks.

    Start with the file system, not the scanner

    A scanner matches signatures; a hand-edited theme file that includes a remote script usually passes. These three structural checks are faster and do not depend on a signature database being current.

    Run them from the WordPress root over SSH, or through your host’s file manager terminal.

    # PHP files inside uploads — there should be none
    find wp-content/uploads -type f -name '*.php'
    
    # Everything modified in the last 30 days, newest last
    find wp-content -type f -name '*.php' -mtime -30 -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort
    
    # Any PHP file that is world-writable, a common attacker trick
    find . -type f -name '*.php' -perm -o+w

    The first command should print nothing. Uploads is for images and documents; a .php file sitting next to your photos was put there by someone, and it is a backdoor.

    The second command is a timeline: compare it with the dates you actually ran an update.

    mu-plugins: the folder that loads and hides

    This is the trick I see most often, and the one most site owners have never heard of. Anything in wp-content/mu-plugins loads on every request with no activation step and no entry in the Plugins list. Good hosts use it for a cache loader or an object-cache drop-in. Attackers use it for a single line of PHP.

    ls -la wp-content/mu-plugins/

    If you do not recognise a filename, read the first twenty lines. A real mu-plugin has a comment header and actual code. A malicious one is often a lone include of a file elsewhere, or one very long base64 string passed to eval, gzinflate or create_function. There is no legitimate reason for a plugin to decode a string and execute it.

    Do not just delete the file. Note its name and look for the same string in the theme and in wp-config.php — a mu-plugin loader is usually called from two or three places so that removing one copy does nothing.

    Fake plugins with real-looking headers

    A dropped plugin differs from a real one in ways that survive a quick glance. It has a proper header block — Plugin Name, Version, Author — because that header is what makes the file a plugin at all. What it lacks is history: no changelog, no readme in the format WordPress expects, and an author URL on a recently registered domain.

    wp plugin list --fields=name,status,version,update
    ls -la wp-content/plugins/

    Compare the two: the command lists what WordPress knows about, the directory shows what is on disk. A folder with no matching row was never installed through the admin — someone dropped it there. A plugin whose files all share one timestamp while the header claims an old version is not one anyone maintained.

    Core files: one line is enough

    Injections into core are small on purpose. A single line at the top of wp-settings.php or index.php, often wrapped in a check for a specific user agent or a cookie, is enough to serve a payload to Googlebot and nothing to you.

    WordPress ships with checksums for every file in every release, and WP-CLI can compare your installation against them:

    wp core verify-checksums

    Clean output means no core file has been edited; warnings name the files that differ. Do not fix it by unpacking a fresh copy over a live site — read the diff first, then replace that one file from the official zip.

    The database footprint: users, options, cron

    Files get you access. The database is what keeps it. Three tables carry almost all of the persistence: wp_users, wp_options, and wp_usermeta with the serialised cron array.

    wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
    wp cron event list --fields=hook,next_run,recurrence
    wp option get siteurl
    wp option get home

    On the user list, look at the registration date, not the name. Attackers pick names that blend in — wp_support, admin_backup, editor2 — but they appear on a date when you were not working. Every administrator should be a person you can name.

    On the cron list, a hook with no plugin behind it is the tell. WordPress will happily schedule an event pointing at a function that a malicious mu-plugin defined; the event itself looks ordinary. If you cannot trace a hook to a plugin you installed, treat it as hostile.

    For options, siteurl and home should be identical and should be the URL you expect. A mismatch is the classic way to keep an admin logged in on one hostname while sending visitors elsewhere.

    Redirects and injected sitemaps

    Some infections never add a PHP file. They edit .htaccess and add a rule that fires only for mobile user agents, or only for traffic arriving from a search engine. The page looks completely normal to you and redirects every visitor from Google to a casino.

    Read .htaccess from the top. Rules inside <IfModule mod_rewrite.c> that reference HTTP_USER_AGENT, HTTP_REFERER, or a specific country code are not part of WordPress — the stock rules only rewrite to index.php.

    The sitemap is the other quiet channel. Check /sitemap.xml and /wp-sitemap.xml against your post list. URLs you never published mean the injection is generating pages on the fly, usually only for a crawler carrying a Google referrer. That is exactly what happened when my own site was hit with Japanese keyword spam — the recovery steps are here.

    Where to look, at a glance

    Where it hides What you see How to confirm
    uploads a .php file beside images find wp-content/uploads -name '*.php'
    mu-plugins unrecognised file, no Plugins entry ls -la wp-content/mu-plugins/
    plugins folder with no matching wp plugin list row diff the list against the directory
    core one edited line in a core file wp core verify-checksums
    users admin registered on a quiet day wp user list --role=administrator
    cron hook that belongs to no plugin wp cron event list
    .htaccess / sitemap redirects or URLs you never published read the rules, compare sitemap to posts

    Removing files is half the job

    The other half is the entry point: an unupdated plugin, a reused password, or a theme that bundles “nulled” premium code. Locking down wp-login closes the front door; it does not remove a key already copied.

    So after you clean the files: change every administrator password, change the database password, regenerate the WordPress salts in wp-config.php (which invalidates every existing login cookie), and revoke any FTP or SSH key you do not recognise. Then check your hosting account for extra FTP users — a leftover FTP account survives a full WordPress reinstall.

    Be careful with the restore: if the last backup already contained the backdoor, restoring it puts you back where you started. Compare files against a known-good copy, not a snapshot from an infected week.

    Then read the access log for the day the files changed — the request that wrote them is in there, with the IP and the path it used. That is how you find the entry point instead of guessing at it.

    What to do today

    Run the three file-system commands and wp core verify-checksums, and save the output to a file on your own machine. That file is your baseline: next time something feels wrong, run the same commands and compare. Ten minutes now beats an afternoon of wondering.