Final Milestones on the Path to v1.0.0: Fortress CLI, Unlimited Config Environments & More

| in


Fortress’s v1.0.0 is purely symbolic at this point.

Plenty of sites are running it in production without a hitch, and frankly, I’d bet a decent amount of money that Fortress is already among the most reliable and heavily tested software in the WordPress ecosystem.

That said, we’ve released two of the remaining big milestones that, in my mind, have always a prerequisite for tagging a v1.0.0 of Fortress:

Let’s dive in.

A new Fortress CLI

An image that shows the output of running "wp fort --help"

This might seem a bit unexpected—after all, Fortress had a CLI since the very first beta release. It was solid, it was tested, and it got the job done.
But, to be honest, it also fell squarely into the category of: “If you’re not embarrassed by the first version, you’ve launched too late.

Fortress has always been a CLI-first product, designed to enable maximum automation in hosting integrations. The initial iteration was “good enough,” but I did not want to commit to & maintain a “good enough” for the lifetime of Fortress; I wanted it to be as reliable and intuitive as the world’s most widely used infrastructure tooling.

That’s where the creators of several popular CLIs (like Docker Compose and Replicate) come in. They’ve put together a blueprint for building exceptional command-line programs: clig.dev – an open-source guide to help you write better command-line programs.

I took this blueprint to heart; here’s everything that’s changed.

Removing verbosity and naming inconsistencies

I have to give credit to Ben Gabler over at Rocket.net for initially pointing this one out.

The following commands:

  • wp snicco/fortress shared config:sources
  • wp snicco/fortress shared config:test
  • wp snicco/fortress auth magic-link:create

…were all terribly named.

In my mind, it all made sense:

  • “snicco” is our company name/namespace, Fortress is the product name.
  • “shared” because this is “shared functionality” between modules.
  • “config:” because it’s related to configuration.

But the truth is: it was incredibly verbose, none of the letters are close to each other on a keyboard, and every command contained at least two special characters.

It takes me almost ten seconds to type out the config:test command, and I know the CLI inside-out.

The new CLI is much improved in this regard. The above commands are now:

  • wp fort config ls
  • wp fort config test
  • wp fort magic-link create

Furthermore, commands are now consistently named based on what they do. For example, you have always been able to list your configuration sources and your currently cached/used configuration.

But for some reason, one was called wp snicco/fortress shared config:sources, while the other was called wp snicco/fortress shared config:cache (???).

config:cache implied caching the current configuration, which isn’t what the command did at all.

Now, all commands that “list” something are named consistently:

  • wp fort config ls
  • wp fort config cache ls

Extensive inline-help, and usage examples

Running wp fort <cmd> --help is now your single source of truth for usage instructions, advanced examples with STDOUT/STDERR output, backward compatibility guarantees for each command, and more.

An image that shows that output of running "wp fort config ls --help"
An image that shows that output of running "wp fort config ls --help",
specifically the manual.

A web version of each command’s --help output is also available online in the Fortress docs, ensuring you have access to the information wherever you need it.

A Three-Tiered System for Confirming Destructive Actions

Previously, all “destructive” actions in Fortress CLI had a simple yes/no confirmation prompt (defaulting to “no”). While this certainly put us in the “good enough for plausible deniability” category, this approach didn’t offer the level of safety and precision I wanted.

The new Fortress CLI introduces a three-tiered system to classify commands by their level of destructiveness, ensuring that you can never perform accidental actions.

1. Optional confirmations

Optional confirmations are designed for actions that aren’t destructive and aren’t necessary for the command to complete.

For example, when running the config test command, if all your configuration sources are valid, Fortress will ask if you want to reload your configuration cache immediately.

An image showing the terminal output where wp fort config test was run.

In automated scripts, these confirmations are also optional, but you can include each step with a specific flag. For instance, to trigger the reload step in wp fort config test, you would use the --reload-on-success flag.

2. Confirm to proceed

This tier is for commands that are more destructive in nature and require explicit confirmation before execution begins. These are actions that could significantly alter your site, so we’ve made sure they can’t be run accidentally.

A good example is the wp fort config cache clear command, which manually deletes your cached configuration without validating your current configuration sources.
Similarly, encrypting all WordPress options with Vaults & Pillars falls into this category.

An image showing the terminal output of running the wp fort config cache clear command.

In automated scripts, these commands require you to pass a --force flag to proceed.

3. Explicit Acknowledgements

Some commands are irreversible (short of restoring a backup) and require an extra layer of safety. To ensure that these important actions are never executed accidentally, we’ve implemented a system that asks you to type a unique acknowledgement into the terminal.

For instance, if you’re resetting all user passwords on a site, you’ll be prompted to type “passwords-will-be-reset-irreversibly” before the command can proceed. This extra step is there to make sure these important actions are intentional and well-considered.

An image showing the terminal output of running "wp fort password force-reset-all"

In automated scripts, these acknowledgements can be provided using the --ack flag, like --ack=passwords-will-be-reset-irreversibly, ensuring that the command is executed carefully even in an automated environment.

From Command Line to Control Panel

Fortress CLI is now perfectly positioned to serve as the foundation for building a simple UI that allows end users to run a curated list of Fortress commands directly from their control panel.

Example: GridPane‘s UI for running a curated list of Fortress commands:

Gridpane Fortress Command Ui

Everything that Fortress can do, can be done with the Fortress CLI; the possibilities are limitless.

Over time, I hope to backport all the low-level CLI concerns out of Fortress and into our open-source BetterWPCLI library—WordPress needs more high-quality command-line tools.

Huge Improvements to Configuration Management

It’s rare for a hosting company or agency to need to modify Fortress’s baseline configuration—it’s optimized to work smoothly for 95% of use cases. But for the truly unique 5%, Fortress has always offered exceptionally granular configurability.

The latest release makes this process easier and more flexible than ever before:

Unlimited configuration sources, scoped to different WordPress environments

Previously, Fortress supported two configuration files that were considered when building a cached configuration: one site-level config file and one optional server-level config file, which could be used to customize the baseline for all sites on a server.

This “restriction” has been completely removed. Now, an unlimited number of configuration sources can be defined, and the best part is: their usage can be limited to specific WordPress environments, such as production, staging, or development.

Configuration sources are defined with a simple PHP constant before Fortress boots:

define('SNICCO_FORTRESS_CONFIG_SOURCES', [
    'server' => [
        'path' => '/etc/fortress/server.json',
        'shared_between_sites' => true,
    ],
    'server.staging' => [
        'path' => "/etc/fortress/server.staging.json",
        'environment' => 'staging',
    ],
    'site' => [
        'path' => "/<path-to-vhost>/config.json",
    ],
    'site.staging' => [
        'path' => "/<path-to-vhost>/config.staging.json",
        'environment' => 'staging',
    ],
]);

The above setup defines four configuration files:

  • A server-level configuration that is always used for all sites on a server: stored at /etc/fortress/server.json
  • A server-level configuration that is used for all sites on a server, but only in staging: stored at /etc/fortress/server.staging.json
  • A site-level configuration stored in the site’s “vhost” directory, which can overwrite any server-level configs.
  • A site-level staging configuration, which is only loaded in staging environments and can overwrite any previous configuration.

This setup allows for more secure and restrictive settings in production, while using less restrictive settings in staging environments.

For example, to completely disable Fortress’s session security module in staging (perhaps because the site is behind network-level IP restrictions), all that’s needed is to add one-line to the /etc/fortress/server.staging.json file:

{
  "modules:except": ["session"]
}

As soon as the site is pushed to staging, the additional config source will be loaded, and the session module disabled.

One line—that’s all it takes now to customize all staging sites on a server.

There’s no longer a need to manually edit configurations on staging and remember to revert changes before pushing back to production.

Why is this not a more common thing in the WP ecosystem?
Most products seem to be based on the idea that people don’t use staging sites. I suppose this probably has some truth.

Easy Extensibility with :locked, :merge, and :except Notations

If you paid attention, you might have noticed that the previous server.staging.json example used a new configuration notation:

{
  "modules:except": ["session"]
}

In the past, each configuration source would replace settings in the previous source on a per-key basis. This made extending a server-level configuration (or Fortress’s baseline) cumbersome if you wanted to adjust settings that had already been defined.

For example, suppose your previous server-level configuration was as follows:

{
  "privileged_user_roles": [
    "administrator",
    "editor",
    "shop_manager"
  ]
}

Now, you add a new site to the server, but you also want authors to be considered high-privileged user roles.

Previously, you had to redefine the option in the site’s configuration:

{
  "privileged_user_roles": [
    "administrator",
    "editor",
    "shop_manager",
    "author"
  ]
}

This works, but now your server-level and site-level configurations are out of sync. If you add a new role to the server-level config, it won’t be reflected in the site-level configuration because you’re completely overwriting the setting.

The latest Fortress release introduces three special notations to simplify these scenarios:

  • :except – Removes the specified values while keeping all other values.
  • :merge – Merges the specified values with all previous values.
  • :locked – Ensures that subsequent config sources can no longer change this setting. This is particularly useful for hosting providers who need to lock down specific settings that must remain unchanged.

Now, you can simply do the following at the site level:

{
  "privileged_user_roles:merge": [
    "author"
  ]
}

This has the significant advantage of keeping the site configuration in sync with any future changes made at the server level.

The opposite is also true. Suppose you want to use all of Fortress’s default protected capabilities of the Sudo Mode, except for the delete_posts capability.

Previously, the only option was to copy the entire baseline values into your site’s configuration and then remove the delete_posts capability. While this works, it means you wouldn’t receive new protected capabilities that might be defined in future Fortress versions.

Now, you can do the following:

{
  "session": {
    "protected_capabilities:except": ["delete_posts"]
  ]
}

Refer to the documentation for more details on the special notations.

You Never Have to Touch Configuration Files by Hand Again

One of the most significant improvements in the latest Fortress release is the ability to edit configuration sources programmatically with the Fortress CLI.

Fortress now includes the config update command, which allows you to manage your site’s configuration sources directly from the CLI.

If you choose, you’ll never have to manually edit your configuration files again!

Fortress Config Update Simple Example

The config update command:

  • Can perform every Fortress configuration operation, including additions, deletions, appending, :except notation, :merge notation, and more.
  • Includes intelligent auto-corrections for mistyped option names.
  • Supports multiple atomic operations in a single command invocation.
  • Automatically tests your configuration for validity—ensuring that it’s impossible to mess up your settings, as every change goes through the full config validation cycle.
  • Reloads your configuration automatically after updating.
  • Offers the ability to preview changes with a --dry-run flag.
  • And much more.

Let’s look at a simple example: updating the Fortress session idle timeout to one hour (3600 seconds):

wp fort config update session.idle_timeout=3600

For a more complex example, suppose your starting configuration looks like this:

{
  "privileged_user_roles": [
    "administrator"
  ],
  "auth": {
    "require_2fa_for_roles": [
      "administrator",
      "editor"
    ],
    "skip_2fa_setup_duration_seconds": 1800
  }
}

Running the following command:

wp fort config update --dry-run \
  code_freeze.enabled=auto \
  privileged_user_roles+=author \
  auth.require_2fa_for_roles-=editor \
  -auth.skip_2fa_setup_duration_seconds 

This reads as:

  • “Set the code_freeze.enabled option to auto.”
  • “Then, add author to the privileged_user_roles list.”
  • “Then, remove editor from the auth.require_2fa_for_roles list.”
  • “Then, remove the auth.skip_2fa_setup_duration_seconds option.”

The resulting configuration would be:

{
  "privileged_user_roles": [
    "administrator",
    "author"
  ],
  "auth": {
    "require_2fa_for_roles": [
      "administrator"
    ]
  },
  "code_freeze": {
    "enabled": "auto"
  }
}

Refer to the full documentation, or run wp fort config update --help to see many more usage examples.

It’s impossible to mess up—every configuration change is validated in the same way as the config test command.

Automatic, Context-Aware Config Optimization

Fortress now includes the config optimize command, which automatically optimizes configuration sources based on the current state of the WordPress site.

Here are two concrete examples:

  • Disabling support for legacy WordPress hashes
  • Disabling WordPress application passwords

Fortress allows you to proactively rehash and upgrade your users’ stored passwords to its secure password hashing. Once all user password hashes have been upgraded, you can disable support for legacy MD5-based WordPress hashes, which offers significant security benefits.

Previously, you had to remember to apply this optimization after upgrading all users.
Now, you can simply run config optimize periodically, and Fortress will automatically determine the best configuration for you—no mental effort required.
If even one user still has a legacy password hash, Fortress will hold off on changing the configuration. However, as soon as all users are upgraded, legacy password hashes will be disallowed.

It works similarly for WordPress Application Passwords, which were previously disabled by default because it’s easy to trick a site owner into creating a malicious one. While this added protection was important, it created an extra step for the small percentage of Fortress sites that relied on these passwords for legitimate purposes—they had to be manually re-enabled.

Now, with the config optimize command, application passwords are enabled by default but will be automatically disabled if no user in the database has a configured application password. This approach achieves the same outcome for sites without application passwords but removes unnecessary steps for the few that do use them.
You might even discover an application password in your database that hasn’t been used in years 😄.

Over time, the goal is for config optimize to handle nearly all configuration concerns automatically. I think this is the ideal level of abstraction for agencies when it comes to security configurations—running a single command (possibly with a UI on top) rather than dealing with a complex, twenty-page setup wizard.

config optimize is 100% safe to run as a CRON job, or to expose it through a simple UI in a hosting control panel:

A screenshot showing GridPane's UI for running the wp fort config optimize command.
GridPane‘s UI for optimizing Fortress configuration.

Support for PHP Configuration Files

This one is quick and easy. While JSON is a great format for configurations managed by programs (such as config update), it has its shortcomings when configurations are created and managed manually by developers—most notably, the lack of support for comments.

With the latest Fortress release, you can now write any configuration source as a plain PHP file, as long as the file path ends with .php.

These two configurations are fully equivalent:

{
  "auth": {
     "require_2fa_for_roles": ["administrator"]
  }
}
<?php

return [
  'auth' => [
    'require_2fa_for_roles' => ['administrator']
  ]
];

Refer to the documentation for more information.

Enhanced Support for Testing Configuration in CI/CD Pipelines

Some agencies use Fortress on sites that are deployed via CI/CD pipelines onto fully immutable file systems. Previously, this presented a few challenges:

  • a) Configuration could not be edited in production—neither with config update nor manually.
    b) Fortress’s extensive config validations might pass locally but raise warnings against the production database.
  • c) Running config test in CI/CD pipelines wasn’t feasible because it assumed stateful tests could be performed against the database, which isn’t possible in a CI/CD environment.

The ideal solution would:

  1. Allow configuration testing in CI/CD pipelines while skipping all “stateful” checks that interact with the WordPress environment.
  2. Enable the “preview” of configuration changes in production to verify their compatibility. For example, before disabling support for legacy password hashes, it’s essential to know if any users in the production database still rely on these hashes—something that can’t be determined from a local environment.

Both of these goals are now possible due to enhancements in the config test command.

First, you can now pass the --skip-stateful-checks flag to config test, making it possible to run this command in CI/CD pipelines.

A screenshot showing the terminal output of running config test with the --skip-stateful flag.

Additionally, to “preview” configuration changes in immutable production environments, config test now supports reading configuration sources from STDIN. This allows you to temporarily replace the real configuration stored on the file system during the config test command, making it possible to see if deploying a configuration change is safe against the production database.

For example, to check if you can safely disable support for WordPress legacy password hashes, you can run:

echo '{"password": {"allow_legacy_hashes": false}}' | wp fort config test --stdin-source=site
Fortress Config Test From Stdin

In this example, Fortress identifies that one user still has a legacy password, so deploying this configuration would not be safe yet.
Refer to the documentation for more information about Fortress and CI/CD deployments.

So, What’s Left for v1.0.0?

Not much! Apart from a few minor tweaks that aren’t worth mentioning, we’re almost there. Here’s what’s still on the menu:

Hard Requirements:

  • Official Version Policy: Finalizing an official version policy for future PHP and WordPress compatibility, including the possibility of LTS versions of Fortress.
  • Secret Management Milestone: A major paradigm change for secret management for WordPress—details are under wraps for now, but it’s a big one.

Under Consideration:

  • Session Module Overhaul: Significant improvements to the entire session module. If these don’t make it into v1.0.0, they’ll be rolled out in a subsequent 1.X version.

In any case, Fortress v1.0.0 is on track for a Q4 2024 release.

To stay up to date with the latest announcements, enter your best email below.

Fortress v1.0.0