Skip to content

v3.6.0 - The Great Reset

Latest

Choose a tag to compare

@joglomedia joglomedia released this 27 Oct 06:07

[3.6.0] - 2026-10-27

Added

  • PHP-FPM and Nginx audit hardening:
    • Adaptive resource profiles (micro, small, medium, large) that size
      PHP-FPM workers, OPcache, and Nginx from detected RAM and CPU.
    • nginx-lowend.conf template for hosts with less than 1 GiB RAM.
    • Opt-in rate limiting, archive-file deny rules, ETag control, and secure
      session-cookie handling (COOKIE_SECURE).
  • TUI live log streaming for Nginx, PHP-FPM, MariaDB, auth, and Fail2ban logs.
  • Interactive site-creation wizard in the TUI.
  • lemper-cli modsec plugin with status, report, and tune --auto commands
    for OWASP CRS paranoia/threshold tuning with nginx -t validation and rollback.
  • systemd-resolved DNS cache flushing around ACME DNS-01 issuance and renewal
    deploy hooks.
  • SEC-20 through SEC-26 hardening fixes and the SEC-27 PHP2Shell entry in the
    security register.
  • Opt-in rewrite_log (NGINX_REWRITE_LOG) for debugging rewrite rules.
    Enabling it also raises the Nginx error log from warn to notice, since
    rewrite messages are logged at notice level (issue #169).
  • Opt-in HTTP/3 (QUIC): NGINX_HTTP3=true adds QUIC listeners, http3 on,
    and an Alt-Svc header to TLS vhosts. Source builds compile the v3 module
    when a QUIC-capable TLS library is selected, and the installer detects the
    module and skips HTTP/3 when a repository build lacks it (issue #175).
  • CORS can be enabled or disabled per vhost with
    lemper-cli site mod --enable-cors and --disable-cors (issue #52). The
    generated include defaults to a wildcard origin and can be restricted by
    editing its set $cors line.
  • CI installs and verifies the FTP server (issues #135 and #146). The Ubuntu
    job checks the repository-installed Pure-FTPd service, TLS bundle, and auth
    links, and a new ftp-source matrix compiles Pure-FTPd 1.0.54 and VSFTPD
    3.0.5 from source on Ubuntu 22.04 and 24.04.
  • lemper-cli backup archives and restores the configuration LEMPer manages:
    /etc/lemper, Nginx, PHP, MySQL/MariaDB, PostgreSQL, MongoDB,
    Redis/Valkey, Memcached, Fail2ban, CrowdSec, ModSecurity tuning, Let's
    Encrypt, mail, FTP, systemd units, and legacy .env files. create,
    list, show, verify, restore, and prune subcommands are included.
    Restore validates archive members against an allow-list, writes a
    pre-restore snapshot, tests Nginx afterwards, and rolls back on failure.
    Archives contain secrets and are created root-only (0600).
  • lemper-cli update php --migrate-to=VERSION moves every vhost that still
    uses an EOL PHP onto that version. --force=VERSION is the same switch;
    a bare --force is rejected so the destination stays explicit. EOL
    fastcgi_pass sockets and EOL set $php_version lines are rewritten.
    The pool user is taken from phpX.Y-fpm.USER.sock (a bare socket uses
    www). A missing destination pool is copied from the EOL pool, or written
    from a small template when that file is gone. Existing destination pools
    are kept. Nginx is tested before reload, and the previous vhosts are stored
    under /etc/lemper/backups/php-migrate-<UTC>/. The old PHP packages and
    pools stay installed.

Changed

  • Default PHP installs are 8.2, 8.3, 8.4, and 8.5. PHP_VERSIONS in
    .env.dist no longer lists 8.1, and the interactive installer menu
    matches that set. An explicit 8.1 (or any other unsupported version) in
    PHP_VERSIONS or --php-version is skipped.
  • Repository nginx installs use the MyGuard nginx-only APT source
    (https://deb.myguard.nl/apt/nginx/<codename>) with a scoped signing key
    and a pin that covers nginx packages only. NGINX_REPO_SRC=ondrej selects
    that source; the Ondřej nginx archive is no longer published.
  • ./update.sh and lemper-cli update lemper reinstall the administration
    tools by running scripts/install_tools.sh from the recorded checkout.
    That refreshes the CLI, every plugin, Adminer, TinyFileManager, and the
    PHP admin panels. The checkout .env is used when it exists; otherwise
    the installed /etc/lemper/lemper.conf is used. --check still audits
    without reinstalling those tools. --install-only reinstalls the tools
    and does not upgrade stack packages.
  • Install and remove scripts share the 2014-2026 license header and resolve
    the checkout root before loading helpers, so a standalone run no longer
    depends on the caller's working directory. install.sh, remove.sh, and
    lemper.sh answer --help before asking for root. The CrowdSec installer
    downloads the upstream release tarball and runs wizard.sh --bininstall
    on amd64 (other architectures compile the tagged source), installs the
    current hub collections, and saves the repository setup script instead of
    piping it to bash. Removal stops an inactive CrowdSec unit without
    aborting, deletes wizard-installed binaries and plugins, and runs
    apt-get autoremove only after CrowdSec packages were purged.
  • All CLI plugins now share one license header (copyright 2014-2026), declare
    CMD_PARENT/CMD_NAME, and expose --help/--version. lemper-encrypt is
    a library only; it is no longer installed as a no-op CLI plugin. The install
    and update paths create the advertised create/add/manage/mod aliases,
    plus the missing site db/site database links.
  • lemper-cli bench was rewritten. The old script parsed wget -q output,
    which suppresses the progress line, so every download reported an empty
    speed. It now uses curl's speed counter with a per-endpoint timeout,
    downloads current Cloudflare/Vultr/OVH endpoints, reports failed endpoints
    instead of blanks, handles GNU/BusyBox/BSD dd output, and cleans up its
    disk test file. New --hardware, --disk, --network, --size,
    --disk-size, and --timeout options replace the all-or-nothing run.
    The TUI gains a Benchmark menu for quick, network-only, and full runs.
  • lemper-cli --help now nests subcommands one level under their parent
    command instead of padding them to the description column. update check
    prints an aligned PHP status table with column widths computed from the
    installed versions, state, and fixed-release values.
  • Replaced the deprecated AdminerEvo (4.8.4) web database manager with upstream
    Adminer 6.2.0, configurable through
    ADMINER_VERSION in .env (issue #183). Existing /lcp/dbadmin files are
    overwritten on upgrade. The install recap no longer checks undefined
    INSTALL_ADMINER/INSTALL_FILEMANAGER flags and lists both tools when
    Nginx is installed.
  • Reorganized the Nginx configuration to remove repeated boilerplate (issue
    #29). The default and admin vhosts now pull PHP-FPM handlers from
    includes/php_handlers.conf and the loopback status endpoints from
    includes/monitoring.conf instead of repeating 18 handler and status blocks
    in each file. The security/restriction/static-file include order is
    documented in etc/nginx/includes/README.md and ARCHITECTURE.md, and stale
    comments in nginx.conf and nginx-lowend.conf now point at the real
    per-vhost include paths.

Removed

  • PHP 8.1 from the default install set and the shipped configuration.
    etc/php/8.1/ is gone, the .php81 Nginx handler is gone, and
    /lcp/phpinfo.php81 is no longer created. remove_php.sh can still
    uninstall a PHP 8.1 that is already on the host. PHP 8.1 is EOL and has
    no vendor patch for PHP2Shell.
  • Node.js from the planned installer list. Bun.js is the JavaScript runtime.
  • Ubuntu 20.04 (Focal) and Linux Mint 20 support. The preflight check now
    rejects them; the minimum supported Ubuntu release is 22.04 (Jammy).
  • Remaining PageSpeed references from the developer docs (ROADMAP, AGENTIC).
    The archived Nginx PageSpeed module was already removed; no PageSpeed
    module, directive, package, or cache configuration remains (issue #178).

Fixed

  • lemper-cli update php no longer aborts a branch upgrade when
    phpX.Y-json is installed. Current Ondřej packages publish that name
    only as a virtual package provided by the FPM, CLI, and CGI SAPIs, so
    apt reports "no installation candidate" and upgrades nothing else on
    the branch. The retired JSON package is removed, and any other PHP
    package without a candidate is skipped.
  • Repository Nginx installs no longer request libmaxminddb-dev. Ubuntu's
    development package requires the distro libmaxminddb0 exactly, while the
    MyGuard nginx archive publishes a newer library that the GeoIP modules
    already depend on. Source builds still install the headers themselves.
  • lemper-cli update no longer aborts when a legacy Ondřej nginx source
    returns 403. Sources for ondrej/nginx, ondrej/nginx-mainline, and
    packages.sury.org/nginx are disabled before apt-get update. The Ondřej
    PHP repository stays enabled. update nginx and the nginx step of
    update all then enable the MyGuard nginx-only repository, pin those
    packages to deb.myguard.nl, and upgrade them while keeping existing
    /etc/nginx files (--force-confold). MyGuard's current nginx is mainline
    and links libssl-nginx beside the system OpenSSL. That Ondřej-to-MyGuard
    package switch has not been verified on a live server.
  • lemper-cli tui draws its menu again. The menu text was captured by the
    command substitution that reads the selection, so the terminal showed only
    Select [1-8] or 'q' to quit: and every choice was ignored.
  • lemper-cli fixpermission now requires an explicit path and refuses /,
    /etc, /usr, /var, and other system roots. Previously a missing argument
    silently did nothing and a bare / would have chmod-ed the whole filesystem.
  • lemper-cli account and lemper-cli adduser handle --help/--version
    before touching the system. adduser builds its useradd arguments from
    validated values, so empty options are no longer passed as arguments.
  • lemper-cli selfssl, site list, site show, and update now accept
    -h/--help and -v/--version consistently with the other commands.
  • lemper-cli account validates usernames before using them in useradd,
    sed, and .htpasswd updates.
  • TUI menu actions now dispatch through lemper-cli instead of executing
    plugin files directly. Plugins expect the CLI's helper functions, so direct
    execution was rejected with "Direct access to this script is not permitted."
    Selecting the Exit menu item also quits now; previously only q/0 worked.
  • lemper-cli tui now launches. The plugin only ran when executed directly,
    but the CLI sources plugins, so its entry point never fired and the command
    exited silently. The TUI also accepts --help and --version instead of
    opening the menu.
  • Pure-FTPd now starts reliably (issue #135). The installer creates the ftp
    system account when it is missing, writes each setting to
    /etc/pure-ftpd/conf (the directory pure-ftpd-wrapper actually reads),
    generates /etc/ssl/private/pure-ftpd.pem as a certificate and private
    key bundle instead of pointing TLS at a certificate-only snakeoil PEM, and
    verifies the service after restarting it. Source installs get a native
    systemd unit instead of a copied sysv-generator unit in /run. The packaged
    sysv init script is forced into standalone mode, because inetd mode exits
    successfully without listening on port 21. Re-running the installer
    re-applies this configuration, so hosts that already hit the bug are
    repaired in place instead of being skipped.
  • VSFTPD can be compiled from source on OpenSSL 3 hosts (issue #146). The build
    now allows deprecation warnings instead of failing on -Werror, installs
    libssl-dev/libpam0g-dev/libcap-dev, and the source branch no longer
    falls back to the repository package behind the user's back. TLS is
    configured for TLS 1.2/1.3 and falls back to a generated self-signed
    certificate when no certificate pair exists. The build also finds libpam in
    the multiarch directory, so PAM links on arm64.
  • Pure-FTPd source installs target 1.0.54, drop the unneeded MySQL client
    dependency, and no longer install a stale pure-ftpd.conf that the packaged
    service ignores.
  • lemper.sh now runs from its own checkout instead of cloning a nested copy
    of the repository and switching it to master on every start-up. It resolves
    the repository root from the script location, enters it before using relative
    paths, and answers --help before the root and .env checks, so
    ./lemper.sh --help no longer fails with "dotenv file doesn't exist".
    BASE_DIR in lib/core/lemper-stacks.sh is now derived from that module's
    location, and scripts/utils.sh locates .env next to scripts/ instead of
    rewriting a guessed path with sed.
  • Re-running the installer now reconciles PHP_EXTENSIONS for PHP versions
    that are already installed instead of skipping them entirely. MongoDB, Redis,
    and Memcache are enabled for both the CLI and FPM SAPIs through phpenmod
    and verified with php-fpm<version> -m; the previous helpers only acted when
    the CLI already reported the module, which left extensions invisible to
    Nginx/PHP-FPM (issue #180).
  • .env values that contain spaces and = padding are parsed intact, so a
    4096-bit OpenSSH public key no longer aborts installation with
    xargs: unmatched double quote or unexpected EOF while looking for matching '"' (issues #42, #144). The parser also ignores CRLF line endings
    and lines without a key=value separator.
  • lemper.sh --with-ssh-pub-key now writes RSA_PUB_KEY, the key that
    server_security.sh actually reads, through the new set_env_value helper
    instead of an unescaped sed replacement. Key validation accepts RSA,
    Ed25519, ECDSA, and FIDO keys instead of matching any string containing
    ssh-rs, and option handling now loads the helper functions before parsing
    flags.
  • Uninstall no longer removes packages and configuration for services that
    predate LEMPer. Installers record provisioned services under
    /etc/lemper/installed/, and the Nginx remover leaves a pre-existing server
    untouched unless FORCE_REMOVE=true (issue #182). The README now states that
    LEMPer targets clean installations.
  • ModSecurity installation now uses the packages the nginx repository actually
    ships: libnginx-mod-http-modsecurity and modsecurity-crs (issue #177).
    Previously the installer selected a non-existent package name, created an
    invalid modules-enabled symlink, and pointed the rules at a
    crs-setup.conf that only exists as .example after cloning. The connector
    is now enabled through the package's modules-available file, CRS is
    configured under /etc/modsecurity/crs, and audit logs are written to
    /var/log/modsecurity/modsec_audit.log. Removal purges the same packages and
    strips the include from sites-available vhosts without breaking the
    sites-enabled symlinks.
  • SSL-enabled vhosts created by lemper-cli site mod --enable-ssl now receive
    http2 on;. HTTP/2 uses the non-deprecated directive form everywhere; the
    shipped default-ssl vhost already used it (issue #171).
  • Restored the executable bit on update.sh, which the CI test suite asserts and
    the bootstrap relies on to run directly.
  • Configured bashate to skip the project's established legacy formatting codes
    (E001, E002, E003, E010, E011, E020) so the lint gate matches the existing
    shell style.
  • Repository-based Nginx installs now honor NGINX_REPO_SRC (default
    myguard). The setting was previously ignored in favor of the Ondřej PPA,
    which no longer serves packages; the sury nginx repository is likewise gone.
  • Nginx post-install now copies etc/nginx/cors to /etc/nginx/. The main
    configuration includes that file, so skipping the copy made nginx -t fail
    with open() "/etc/nginx/cors" failed.
  • Valkey installs now use the distribution package when available (Ubuntu
    24.04+, Debian 13) and enable bookworm-backports on Debian 12. The previous
    external repository and GitHub-hosted key URL are no longer published.
  • The shunit2 Redis/Valkey check now verifies valkey-cli and valkey-server
    when REDIS_BACKEND=valkey, instead of assuming the redis-* binaries exist.
  • Nginx TLS setup now generates DH parameters from the predefined RFC 7919
    ffdhe2048 group instead of running a CPU-heavy prime search. The previous
    code used KEY_HASH_LENGTH=4096, which produced dhparam-4096.pem while
    ssl.conf requires dhparam-2048.pem, so nginx -t failed. Configure the
    group with DH_PARAM_GROUP.
  • Valkey installs no longer overwrite the distribution's /etc/valkey/valkey.conf
    with the Redis template. The template pointed at /var/lib/redis and
    /var/log/redis, which the valkey user cannot access, so
    valkey-server.service failed to start. LEMPer settings are now appended to
    the packaged config, and a temporary template is only used for source installs.
  • Missing MongoDB and Redis PHP extensions are now reported as warnings instead
    of fatal errors, because they are optional components.
  • Config-at-rest encryption now uses AES-256-CBC, which openssl enc supports.
    The previous AES-256-GCM path failed with AEAD ciphers not supported, so
    save_config silently discarded every credential, including the MariaDB root
    password. Encrypted-file detection now also recognizes the base64
    U2FsdGVkX1 header produced by openssl enc -a, and the CLI trusts that
    header instead of relying on ENCRYPT_CONFIG_AT_REST, which sudo strips
    from the environment.
  • Repository-based Nginx installs now install the enabled libnginx-mod-*
    packages. A duplicate install_nginx_from_repo definition in
    nginx_build.sh shadowed the one in nginx_repo.sh and captured the
    stderr-only module list into an empty variable, so only the base nginx
    package was installed and stream {} failed with unknown directive.
  • The Nginx stream block is only appended when the stream module is actually
    available (static build or enabled load_module config), so nginx -t
    succeeds even when NGX_STREAM=true but the module is missing.
  • Config encryption now resolves /etc/lemper/.config_key before
    LEMPER_PASSWORD. The installer encrypted with the admin password while the
    sudo-launched CLI only had the key file, so lemper-cli could not decrypt
    /etc/lemper/lemper.conf. Decryption failures now print an error instead of
    silently exiting under set -e.
  • WordPress site creation passes --skip-check to wp config create. WP-CLI
    otherwise validates the deliberately masked __LEMPER_DB_PASS__ placeholder
    against the database and fails with Access denied, before the real password
    is substituted into wp-config.php.
  • The CLI now provides sed_inplace, sanitize_sed_replacement, and the
    resource-sizing helpers used by the site plugin. lemper-cli site add
    previously ran those functions only when sourced by the installer, so the
    plugin failed with command not found.
  • Removed the PECL/PEAR fallback from install_php.sh and stopped installing
    php-pear, dh-php, and pkg-php-tools. Every extension in the default
    PHP_EXTENSIONS set is packaged by the Ondřej/Sury repository, while the
    PECL path emitted PEAR Cannot use bool as array warnings under PHP 8.5 and
    could abort the install on abandoned modules that no longer build. Extensions
    that resolve to no package are now listed in a warning with a pointer to PIE
    for manual installation.
  • The extension resolver skips json (PHP 8.0+) and opcache (PHP 8.5+,
    where it is built into the PHP binary) instead of reporting them as missing
    packages. PHP 8.5 has no php8.5-opcache package.
  • geoip and mcrypt were removed from the default PHP_EXTENSIONS list.
    Both are abandoned upstream and are not installed automatically.
  • Documented that PIE is the manual path, not an installer dependency:
    php/pie requires Composer package names (vendor/package) and cannot
    target Debian's per-version php_suffix layout, so it is not a drop-in
    replacement for this installer's bare extension names. See ARCHITECTURE.md.
  • CI workflows use actions/checkout@v6 (Node 24) so the runner no longer
    prints the Node 20 deprecation warning.
  • Fail2ban installs set PYTHONWARNINGS=ignore::SyntaxWarning for the package
    install. The distribution package byte-compiles its own test files and emits
    SyntaxWarning: invalid escape sequence on newer Python; the warnings are
    upstream noise, affect no running service, and no longer clutter CI logs.