Search by

beziworld / laravel-hestiacp

kamcw

Laravel client for the Hestia Control Panel API: users, web domains, DNS, mail, databases, cron, backups and Let's Encrypt.

Package info

github.com/Beziworld/laravel-hestiacp

Homepage

Issues

pkg:composer/beziworld/laravel-hestiacp

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

v1.0.0 2026-11-10 20:32 UTC

This package is auto-updated.

Last update: 2026-11-10 21:15:34 UTC


README

tests Latest Version License

Laravel client for the Hestia Control Panel API: users and packages, web domains with aliases, redirects and FTP accounts, Let's Encrypt and SSL settings, DNS zones and records, mail domains, accounts, aliases, forwards, catch-all and DKIM, databases, cron jobs, backups and server information, for one or many servers.

Polska wersja dokumentacji

Features

  • Access keys (access_key + secret_key, or hash = ACCESS:SECRET) as recommended since Hestia 1.6, plus the deprecated API key and admin password methods for older servers
  • Exit code handling for both methods: with access keys the Hestia-Exit-Code header, with the legacy methods returncode=yes for changes and the Error: prefix for reads, because those answer HTTP 200 for failed commands
  • Typed CommandException with the exit code and its name (E_EXISTS, E_NOTEXIST, E_LIMIT, ...) and helpers such as alreadyExists() and notFound()
  • Typed results (User, WebDomain, DnsDomain, DnsRecord, MailDomain, MailAccount, Database, CronJob, Backup) built from the JSON output of the v-list-* commands, with dates in the server time zone
  • Read commands (v-list-*, v-get-*, v-search-*) are repeated once after a connection error or HTTP 5xx; every other command is sent exactly once
  • Multiple named connections plus runtime connections with HestiaCP::using([...])
  • query() and execute() for any of the 400+ v-* commands, php artisan hestiacp:check
  • PHP 8.3+, Laravel 12 and 13, PHPStan level 8

Why this package

The PHP clients on Packagist (gigabait/hestiacp-api, neto737/hestiacp-api, ponasromas/hestiacp-api and forks) and the two Laravel packages (teaminfinitylk/laravel-hestiacp-sdk, 14 downloads, and kitteam/hestia-api, 3 downloads, in November 2026) send commands but leave the different answers of the authentication methods to you: with the deprecated methods a failed command is still HTTP 200. This package follows the API source (web/api/index.php) and the official return code table, maps the keyed JSON output of the list commands to data objects and adds Laravel configuration, Http::fake() support for your own tests and an Artisan check.

Installation

composer require beziworld/laravel-hestiacp
php artisan vendor:publish --tag=hestiacp-config   # optional
HESTIACP_URL=https://panel.example.com:8083    # /api/ is appended
HESTIACP_ACCESS_KEY=AKIA0123456789ABCDEF
HESTIACP_SECRET_KEY=your-40-character-secret-key
HESTIACP_TIMEZONE=Europe/Warsaw                # server time zone for DATE and TIME values
HESTIACP_VERIFY_SSL=true
php artisan hestiacp:check

Create the access key as the admin user in the panel or with v-add-access-key admin '*' laravel json on the server, limit it to the commands you need when possible, and add the address of your application to the allowed IP addresses of the API in the server settings.

Usage examples

Provision a hosting account after an order is paid

use Beziworld\HestiaCP\Exceptions\CommandException;
use Beziworld\HestiaCP\Facades\HestiaCP;

class ProvisionHosting implements ShouldQueue
{
    public function handle(): void
    {
        $user = $this->order->username;

        try {
            HestiaCP::users()->create($user, $password = Str::password(16), $this->order->email, package: 'business');
            HestiaCP::domains()->create($user, $this->order->domain);
            HestiaCP::dns()->create($user, $this->order->domain, config('hosting.ip'));
            HestiaCP::email()->createDomain($user, $this->order->domain);
            HestiaCP::email()->createAccount($user, $this->order->domain, 'office', Str::password(16), quotaMb: 2048);
            HestiaCP::ssl()->letsEncrypt($user, $this->order->domain, ['www.' . $this->order->domain]);
        } catch (CommandException $e) {
            // e.g. "Hestia v-add-user failed (E_EXISTS): user john exists"
            $this->fail($e);

            return;
        }

        Mail::to($this->order->email)->send(new HostingReady($this->order, $password));
    }
}

Users

$users = HestiaCP::users()->all();                 // list<User>
$user = HestiaCP::users()->get('john');            // ?User, null when it does not exist

HestiaCP::users()->suspend('john');
HestiaCP::users()->unsuspend('john');
HestiaCP::users()->changePackage('john', 'premium');
HestiaCP::users()->changePassword('john', $newPassword);
HestiaCP::users()->packages();                      // packages with their limits
HestiaCP::users()->delete('john');                  // with all domains, mail and databases

Web domains, FTP and Let's Encrypt

HestiaCP::domains()->create('john', 'example.com');                         // default IP, www alias
HestiaCP::domains()->addAlias('john', 'example.com', ['shop.example.com']);
HestiaCP::domains()->changeBackendTemplate('john', 'example.com', 'PHP-8_3');
HestiaCP::domains()->redirect('john', 'example.net', 'https://example.com');

foreach (HestiaCP::domains()->all('john') as $domain) {
    // $domain->domain, $domain->aliases, $domain->ssl, $domain->letsEncrypt, $domain->diskUsedMb
}

HestiaCP::ftp()->create('john', 'example.com', 'deploy', $password, 'public_html');   // becomes john_deploy

HestiaCP::ssl()->letsEncrypt('john', 'example.com', ['www.example.com']);
HestiaCP::ssl()->letsEncrypt('john', 'example.com', mail: true);       // mail.example.com
HestiaCP::ssl()->forceHttps('john', 'example.com');
HestiaCP::ssl()->certificate('john', 'example.com');                   // NOT_AFTER, ISSUER, ...

DNS

HestiaCP::dns()->create('john', 'example.com', '192.0.2.10', ['ns1.example.com', 'ns2.example.com']);

HestiaCP::dns()->addRecord('john', 'example.com', 'www', 'A', '192.0.2.10');
HestiaCP::dns()->addRecord('john', 'example.com', '@', 'MX', 'mail.example.com.', priority: 10);
HestiaCP::dns()->addRecord('john', 'example.com', '_dmarc', 'TXT', 'v=DMARC1; p=quarantine', ttl: 300);

$mx = HestiaCP::dns()->findRecords('john', 'example.com', '@', 'MX');
HestiaCP::dns()->updateRecord('john', 'example.com', $mx[0]->id, '@', 'MX', 'mail2.example.com.', priority: 20);
HestiaCP::dns()->deleteRecord('john', 'example.com', $mx[0]->id);

Mail

$mail = HestiaCP::email();

$mail->createDomain('john', 'example.com', antispam: true, antivirus: true, dkim: true);
$mail->createAccount('john', 'example.com', 'office', $password, quotaMb: 2048);   // null: unlimited
$mail->changeQuota('john', 'example.com', 'office', 4096);
$mail->addAlias('john', 'example.com', 'office', 'sales');
$mail->addForward('john', 'example.com', 'office', 'boss@example.net');
$mail->setCatchAll('john', 'example.com', 'office@example.com');

$mail->dkimRecord('john', 'example.com');   // ['name' => 'mail._domainkey', 'ttl' => 3600, 'value' => 'v=DKIM1; ...']
$mail->accounts('john', 'example.com');     // list<MailAccount>

Databases, cron and backups

HestiaCP::databases()->create('john', 'shop', 'shop', $password);   // creates john_shop with user john_shop
HestiaCP::databases()->changePassword('john', 'john_shop', $rotated);

HestiaCP::cron()->create('john', '* * * * *', 'php /home/john/web/example.com/app/artisan schedule:run');

HestiaCP::backups()->schedule('john');                       // queued, the user is notified by e-mail
$latest = HestiaCP::backups()->latest('john');               // ?Backup
HestiaCP::backups()->restore('john', $latest->file, web: 'example.com', dns: '', mail: '', databases: '*', cron: '', userDirectories: '');

Several servers

// config/hestiacp.php
'connections' => [
    'default' => [...],
    'node2' => ['url' => env('HESTIA2_URL'), 'hash' => env('HESTIA2_HASH')],   // ACCESS:SECRET
],

HestiaCP::connection('node2')->users()->all();

HestiaCP::using(['url' => $server->url, 'hash' => decrypt($server->api_hash)])->system()->info();

Anything else

HestiaCP::query('v-list-user-ips', ['john']);          // "json" is appended
HestiaCP::execute('v-change-user-shell', ['john', 'bash']);
HestiaCP::system()->stoppedServices();

Exceptions

All exceptions extend HestiaCPException:

Exception When
AuthenticationException Credentials refused (exit code 9, E_PASSWORD, or HTTP 401 without an exit code). Never retried. Extends CommandException.
CommandException A command failed or the API could not be reached: command(), exitCode(), errorName(), httpStatus(), output(), notFound(), alreadyExists(), forbidden(), limitReached()
RateLimitException HTTP 429 from a proxy in front of the panel, retryAfter() in seconds
InvalidConfiguration Unknown connection, missing URL / credentials or an invalid time zone

Notes

  • Commands run synchronously on the server and some (user creation, Let's Encrypt, backups) take several seconds: run them in queued jobs. The default timeout is 120 seconds.
  • Hestia prefixes databases, database users and FTP accounts with the user name.
  • Keys of non-admin users only work when the API is enabled for all users (API_SYSTEM = 2) and only for their own objects.

Development

composer install
composer ci   # pint --test, phpstan (level 8), pest

Author

Created and maintained by Kamil Ćwiertnia, beziworld.eu.

Need an integration? Get in touch through beziworld.eu.

Other packages by the author

License

MIT. See LICENSE.md. This package is not affiliated with the Hestia Control Panel project or its maintainers.