Build a self-hosted web hosting control panel that runs on a single Linux server and integrates with Cloudflare through its REST API.
The panel is intended to provide multi-account static website hosting.
The main concept:
Internet
|
v
+--------------+
| Cloudflare |
| DNS + Tunnel |
+------+-------+
|
Cloudflare Tunnel
|
v
+-------------------+
| Hosting VM |
| |
| +-------------+ |
| | Panel | |
| | Laravel API | |
| +------+------+ |
| | |
| v |
| Nginx |
| | |
| +----+----+ |
| | | | |
| Web1 Web2 Web3 |
+-------------------+
The system must support:
- Admin accounts
- Multiple normal users
- User isolation
- Static website deployment
- ZIP upload deployment
- Website management
- DNS record management
- Cloudflare Tunnel integration
- Cloudflare DNS integration
- Resource usage tracking
- Storage quotas
- Website quotas
- Audit logging
- Installation through a single
.shinstaller - Uninstallation
- Upgrade/update mechanism
- Service management through systemd
The application must be designed so that users never receive Cloudflare credentials.
Admin can:
- Create users
- Delete users
- Suspend users
- Activate users
- Set user quotas
- View all websites
- View resource usage
- View server resource usage
- Manage allowed domains
- Configure Cloudflare
- Configure the Cloudflare Tunnel
- View audit logs
- Manage panel settings
Normal users can:
- Login
- View dashboard
- Create websites
- Delete websites
- Deploy static websites
- Upload ZIP files
- View deployment status
- View website storage usage
- Add DNS records
- Edit DNS records
- Delete DNS records
- View their own DNS records
Normal users MUST NOT be able to:
- Access Cloudflare API tokens
- Access Cloudflare account credentials
- Access another user's files
- Access another user's websites
- Modify global Nginx configuration
- Modify the server
- Execute arbitrary shell commands
- Access SSH
- Manage the Cloudflare Tunnel directly
- Manage Cloudflare zones outside the domains allowed by admin
The panel uses Cloudflare zones configured by the administrator.
Example Cloudflare account:
example.com
example.net
myschool.id
The panel synchronizes the available zones.
Users can only select domains from this list.
Example:
Create Website
Website Name:
my-blog
Domain:
example.com
Subdomain:
blog
The resulting hostname:
blog.example.com
Users must not be able to register arbitrary external domains.
Use one Cloudflare Tunnel for the hosting server.
Do NOT create a separate Tunnel for every website in the initial implementation.
Architecture:
Cloudflare
|
v
hosting-tunnel
|
v
127.0.0.1:80
|
v
Nginx
|
+--> website 1
+--> website 2
+--> website 3
Nginx determines the destination based on the HTTP Host header.
Example:
blog.example.com
|
v
Cloudflare
|
v
Tunnel
|
v
Nginx
|
v
/srv/panel/websites/1/1/public
Use the following stack unless there is a strong technical reason to change it.
- Laravel 11+
- PHP 8.3+
- REST API
- Laravel Sanctum
- Laravel Queue
Prefer:
- PostgreSQL
Alternative:
- MariaDB
PostgreSQL is preferred.
- Redis
- Nginx
- Cloudflare API
- Cloudflare Tunnel
- cloudflared
Prefer:
- React
- TypeScript
- Vite
- Tailwind CSS
The frontend communicates only with the Laravel API.
Primary target:
- Debian 13
The installer should detect the OS.
If the OS is unsupported, terminate with a clear message.
Use a dedicated system directory.
Recommended:
/opt/cloudpanel/
Structure:
/opt/cloudpanel/
├── app/
├── backend/
├── frontend/
├── storage/
├── scripts/
├── backups/
├── logs/
└── .env
Website data must NOT be stored inside the application directory.
Use:
/srv/cloudpanel/websites/
Structure:
/srv/cloudpanel/websites/
├── 1/
│ ├── 1/
│ │ └── public/
│ └── 2/
│ └── public/
├── 2/
│ └── 3/
│ └── public/
└── ...
Where:
/srv/cloudpanel/websites/{user_id}/{website_id}/public/
Never run uploaded website files as root.
Recommended ownership:
root:cloudpanel
or a dedicated service account.
The panel application itself must run under a non-root service account.
Example:
cloudpanel
The installer may run as root, but the application runtime must not.
Uploaded files must not be executable by default.
Do not allow uploaded files to become executable server-side scripts.
For static hosting, Nginx must serve files only.
Initial implementation supports static websites only.
Supported:
HTML
CSS
JavaScript
Images
Fonts
JSON
Static assets
Example ZIP:
website.zip
├── index.html
├── css/
├── js/
└── assets/
After deployment:
/srv/cloudpanel/websites/1/5/public/
├── index.html
├── css/
├── js/
└── assets/
The system must reject dangerous archive contents.
Prevent:
- Path traversal
../- Absolute paths
- Symlinks escaping the website directory
- Device files
- Executable files when unnecessary
When a user creates a website:
User
|
| Create website
v
Laravel API
|
+--> Validate quota
|
+--> Validate domain
|
+--> Create database record
|
+--> Create website directory
|
+--> Generate Nginx configuration
|
+--> Create Cloudflare DNS record
|
+--> Queue deployment
|
v
Redis
|
v
Laravel Worker
|
+--> Extract ZIP
|
+--> Validate files
|
+--> Calculate storage
|
+--> Update Nginx
|
+--> Reload Nginx
|
+--> Verify website
|
v
Deployment complete
Deployment should use queues.
Do not perform long-running deployment work directly inside the HTTP request.
Each website should have an isolated Nginx server block.
Example:
server {
listen 127.0.0.1:80;
server_name blog.example.com;
root /srv/cloudpanel/websites/1/5/public;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~ /\.(?!well-known) {
deny all;
}
}The panel must generate these configurations automatically.
Store generated configurations in:
/etc/nginx/sites-available/cloudpanel/
Enable them through:
/etc/nginx/sites-enabled/
Do not let users edit raw Nginx configurations.
Before every reload:
nginx -tOnly reload Nginx if validation succeeds.
Use Cloudflare API Tokens.
Do not use the Global API Key unless absolutely required.
Required permissions should follow least privilege.
The administrator provides:
Cloudflare API Token
Cloudflare Account ID
The backend should validate the credentials.
The token must be encrypted at rest.
Never expose it through:
- API responses
- frontend
- logs
- error messages
- browser storage
Create a dedicated Laravel service:
app/Services/Cloudflare/
Recommended classes:
CloudflareClient.php
CloudflareZoneService.php
CloudflareDnsService.php
CloudflareTunnelService.php
Example abstraction:
$cloudflare->dns()->createRecord(...);
$cloudflare->dns()->updateRecord(...);
$cloudflare->dns()->deleteRecord(...);Do not scatter Cloudflare API calls throughout controllers.
Controllers should call service classes.
When creating a website:
blog.example.com
The backend should create the required DNS configuration.
The preferred architecture is:
blog.example.com
|
v
<tunnel-id>.cfargotunnel.com
with:
proxied = true
The exact Cloudflare Tunnel routing model should be implemented using the current Cloudflare API.
The implementation must not hard-code undocumented API behavior.
The installer must support configuring one Tunnel.
The Tunnel should be installed as a systemd service.
Example:
cloudflared.service
The installer should:
- Install cloudflared
- Verify installation
- Authenticate/configure the tunnel
- Store credentials securely
- Generate configuration
- Enable systemd service
- Start the service
- Verify connectivity
The panel should be able to display:
Tunnel Status
Connected
Disconnected
Error
The panel must not expose tunnel credentials to normal users.
Initial architecture:
Cloudflare Tunnel
|
v
127.0.0.1:80
|
v
Nginx
Do not dynamically create hundreds of cloudflared ingress rules if Nginx can perform hostname routing.
This keeps the Tunnel configuration simple.
Normal users can manage DNS records only for domains they are authorized to use.
Allowed record types initially:
A
AAAA
CNAME
TXT
Optional later:
MX
SRV
CAA
NS
Be careful with:
NS
MX
CAA
because these can affect the entire domain.
Recommended initial policy:
Users can manage only:
A
AAAA
CNAME
TXT
Admin controls whether advanced DNS records are enabled.
Users must not be able to:
- Modify Cloudflare nameservers
- Transfer zones
- Change account settings
- Delete zones
- Change global DNS settings
- Access other users' DNS records unless authorized
Every DNS operation must verify:
authenticated user
|
v
website/domain ownership
|
v
allowed zone
|
v
Cloudflare record
Never trust IDs supplied by the frontend.
Create migrations for at least:
users
roles
domains
websites
dns_records
deployments
resource_usages
cloudflare_connections
audit_logs
system_settings
id
name
email
password
role
status
created_at
updated_at
Roles:
admin
user
id
zone_id
domain
status
created_at
updated_at
id
user_id
domain_id
name
hostname
document_root
status
storage_limit
storage_used
created_at
updated_at
id
user_id
domain_id
cloudflare_record_id
type
name
content
ttl
proxied
created_at
updated_at
id
website_id
user_id
filename
status
error_message
storage_used
started_at
completed_at
created_at
updated_at
Statuses:
pending
processing
success
failed
cancelled
id
user_id
storage_used
website_count
bandwidth_used
updated_at
id
user_id
action
resource_type
resource_id
ip_address
user_agent
metadata
created_at
Never store passwords or API tokens inside audit metadata.
Every user must have configurable quotas.
Example:
max_websites
max_storage
max_dns_records
max_upload_size
Example:
User A
Websites:
5
Storage:
2 GB
DNS Records:
50
Upload:
100 MB
Before creating resources:
Check quota
|
+--> allowed
|
+--> reject
Quota checks must happen server-side.
Admin dashboard should display:
CPU
RAM
Disk
Load Average
Network
Per-user:
Website count
Storage usage
DNS record count
Deployment count
Initial version does not need CPU/RAM isolation because websites are static.
However, the architecture should allow future container-based resource limits.
Use Laravel Sanctum.
Implement:
- Login
- Logout
- Session/token authentication
- Password hashing
- Password reset
- Rate limiting
- Account suspension
Passwords must use Laravel's supported password hashing mechanism.
Never implement custom password hashing.
Use Laravel Policies/Gates.
Every resource must verify ownership.
Example:
$user->can('update', $website);Never rely only on frontend restrictions.
Frontend restrictions are for UX only.
Backend authorization is mandatory.
Use:
/api/v1/
Example:
POST /api/v1/auth/login
POST /api/v1/auth/logout
GET /api/v1/me
GET /api/v1/websites
POST /api/v1/websites
GET /api/v1/websites/{id}
DELETE /api/v1/websites/{id}
POST /api/v1/websites/{id}/deploy
GET /api/v1/websites/{id}/deployments
GET /api/v1/domains
GET /api/v1/dns-records
POST /api/v1/dns-records
PUT /api/v1/dns-records/{id}
DELETE /api/v1/dns-records/{id}
Admin:
GET /api/v1/admin/users
POST /api/v1/admin/users
PUT /api/v1/admin/users/{id}
DELETE /api/v1/admin/users/{id}
GET /api/v1/admin/resource-usage
GET /api/v1/admin/audit-logs
GET /api/v1/admin/cloudflare/status
POST /api/v1/admin/cloudflare/configure
GET /api/v1/admin/tunnel/status
Use consistent JSON:
{
"success": false,
"message": "Website quota exceeded",
"errors": {
"website": [
"Maximum number of websites reached."
]
}
}Successful response:
{
"success": true,
"data": {}
}Dashboard must have separate layouts.
Dashboard
Users
Websites
Domains
DNS
Cloudflare
Server
Audit Logs
Settings
Dashboard
Websites
Deployments
DNS
Account
Users must never see admin navigation.
Show:
Websites: 3 / 5
Storage: 1.2 GB / 2 GB
DNS Records: 12 / 50
Website list:
Name Domain Status
-----------------------------------------
Blog blog.example.com Online
Portfolio me.example.com Online
Docs docs.example.com Deploying
User should be able to:
Create Website
Website Name
Domain
Subdomain
Upload ZIP
[ Deploy ]
After deployment:
Deployment #123
Status:
Success
Domain:
blog.example.com
Storage:
34 MB
Created:
2026-08-18
Never trust:
filename
MIME type
extension
ZIP structure
Validate all uploaded files.
Set maximum upload size.
Reject:
../../etc/passwd
/etc/passwd
symlink -> /etc
device files
Use safe extraction logic.
Recommended:
- Upload to temporary directory
- Inspect archive
- Validate every path
- Reject dangerous entries
- Extract
- Calculate storage
- Move to final directory atomically
Use Laravel Queue with Redis.
Jobs:
DeployWebsiteJob
DeleteWebsiteJob
CreateCloudflareDnsRecordJob
DeleteCloudflareDnsRecordJob
SyncCloudflareZonesJob
CalculateStorageUsageJob
Workers should run under systemd or Supervisor.
Prefer systemd if practical.
Example:
cloudpanel-worker.service
The project MUST provide:
install.sh
The installation experience should be similar to common hosting panels.
Example:
curl -fsSL https://example.com/install.sh | sudo bashHowever, for security, documentation should also recommend:
wget https://example.com/install.sh
chmod +x install.sh
sudo ./install.shThe installer must be idempotent.
Running it twice should not destroy an existing installation.
Installer flow:
./install.sh
|
v
Check root
|
v
Check OS
|
v
Check architecture
|
v
Check internet
|
v
Install dependencies
|
v
Install PHP
|
v
Install PostgreSQL
|
v
Install Redis
|
v
Install Nginx
|
v
Install cloudflared
|
v
Create cloudpanel user
|
v
Create directories
|
v
Deploy application
|
v
Configure .env
|
v
Generate APP_KEY
|
v
Run migrations
|
v
Create admin account
|
v
Configure Nginx
|
v
Configure systemd
|
v
Start services
|
v
Health check
|
v
Installation complete
Support non-interactive installation.
Example:
sudo ./install.sh \
--domain panel.example.com \
--admin-email admin@example.comAlso support interactive mode:
sudo ./install.shInteractive prompts:
CloudPanel Installer
Panel domain:
>
Admin email:
>
Admin password:
>
Database password:
>
Install directory:
[/opt/cloudpanel]
Website directory:
[/srv/cloudpanel/websites]
Passwords must not be displayed.
The installer must check:
OS
Architecture
Root privileges
Disk space
RAM
Internet connectivity
Required ports
Existing Nginx
Existing PostgreSQL
Existing Redis
Do not blindly overwrite existing configurations.
If conflicts exist:
WARNING:
Nginx is already installed.
Options:
1. Use existing Nginx
2. Abort
The installer should prefer reusing compatible existing services.
Create:
cloudpanel.service
cloudpanel-worker.service
cloudpanel-scheduler.service
Example architecture:
nginx
php-fpm
postgresql
redis
cloudflared
cloudpanel-worker
cloudpanel-scheduler
Use:
systemctl enable --now serviceProvide:
cloudpanel doctoror:
/opt/cloudpanel/scripts/doctor.shIt should verify:
[OK] Nginx
[OK] PHP
[OK] PostgreSQL
[OK] Redis
[OK] Laravel
[OK] Queue worker
[OK] Cloudflared
[OK] Cloudflare API
[OK] Storage permissions
Failed checks should provide remediation hints.
Create:
cloudpanelCommands:
cloudpanel install
cloudpanel update
cloudpanel uninstall
cloudpanel status
cloudpanel restart
cloudpanel logs
cloudpanel doctor
cloudpanel user:create
cloudpanel user:delete
cloudpanel cache:clear
cloudpanel migrate
Example:
cloudpanel statusOutput:
CloudPanel Status
Panel : running
Nginx : running
PHP-FPM : running
PostgreSQL : running
Redis : running
Worker : running
Cloudflared : running
Provide:
cloudpanel updateUpdate process:
Backup
|
v
Download new version
|
v
Maintenance mode
|
v
Update dependencies
|
v
Run migrations
|
v
Build frontend
|
v
Restart workers
|
v
Health check
|
v
Disable maintenance mode
Never automatically destroy user websites during update.
Provide backup functionality.
Backup:
Database
Panel configuration
Cloudflare configuration metadata
Application configuration
Do NOT backup:
Cloudflare API tokens in plaintext
Website files may optionally be included.
Command:
cloudpanel backupRestore:
cloudpanel restore backup.tar.gzApplication logs:
/opt/cloudpanel/logs/
Laravel logs:
storage/logs/
System logs:
journalctl -u cloudpanel
journalctl -u cloudpanel-worker
journalctl -u cloudflaredNever log:
passwords
API tokens
session tokens
authorization headers
Record security-sensitive actions.
Examples:
USER_CREATED
USER_SUSPENDED
USER_DELETED
WEBSITE_CREATED
WEBSITE_DELETED
WEBSITE_DEPLOYED
DNS_RECORD_CREATED
DNS_RECORD_UPDATED
DNS_RECORD_DELETED
CLOUDFLARE_CONFIGURED
TUNNEL_UPDATED
LOGIN_SUCCESS
LOGIN_FAILED
Admin can view audit logs.
Mandatory:
- HTTPS for panel
- Secure cookies
- CSRF protection where applicable
- Rate limiting
- Password hashing
- RBAC
- Authorization policies
- Input validation
- Output escaping
- SQL injection protection
- Path traversal protection
- ZIP bomb protection
- Upload size limits
- API token encryption
- Audit logs
- Secure headers
- No secrets in frontend
- No shell command execution from user input
Never use:
shell_exec($request->input('command'));or equivalent behavior.
If system commands are required internally, use predefined commands and strict argument validation.
Cloudflare API credentials belong exclusively to the server.
Recommended:
Cloudflare API Token
|
v
Encrypted storage
|
v
Laravel Cloudflare Service
|
v
Cloudflare API
Never:
Browser
|
v
Cloudflare API Token
Use least-privilege Cloudflare API token permissions.
Every database query involving user-owned resources must enforce ownership.
Bad:
Website::find($id);Preferred:
auth()->user()
->websites()
->findOrFail($id);Admin endpoints can explicitly use unrestricted queries.
Do not rely on frontend filtering.
Website states:
creating
deploying
active
suspended
failed
deleting
deleted
Example:
Create
↓
creating
↓
deploying
↓
active
If deployment fails:
deploying
↓
failed
Deletion flow:
User requests deletion
|
v
Authorization
|
v
Delete DNS record
|
v
Disable Nginx config
|
v
Remove website files
|
v
Delete database record
|
v
Audit log
Deletion should be queued.
Provide optional soft-delete support.
Admin should be able to synchronize zones:
Sync Cloudflare
The panel retrieves available zones and updates:
domains
Never assume the local database is always correct.
Provide:
POST /api/v1/admin/cloudflare/sync
Cloudflare API failures must not corrupt local state.
Example:
Create Website
|
v
Create local record
|
v
Cloudflare API fails
|
v
Mark operation failed
Use transaction boundaries carefully.
For operations spanning local DB + Cloudflare API, use retryable jobs and explicit state machines.
Do not assume distributed transactions.
Cloudflare API calls should support retry for transient failures.
Example:
Attempt 1
↓
Attempt 2
↓
Attempt 3
↓
Failed
Use exponential backoff.
Do not retry permanent authorization errors indefinitely.
Protect:
login
upload
deployment
DNS creation
DNS deletion
Cloudflare operations
from abuse.
Use Laravel rate limiting.
Implement:
Test:
QuotaService
CloudflareService
DnsService
DeploymentService
StorageService
Test:
Authentication
Authorization
Website creation
Website deployment
DNS management
Admin management
Quota enforcement
Test:
IDOR
Path traversal
ZIP traversal
Unauthorized DNS modification
Unauthorized website access
Privilege escalation
Cloudflare API integration should be mockable.
Do not require a real Cloudflare account for normal CI tests.
Provide:
docker-compose.yml
for local development.
Development services:
Laravel
React/Vite
PostgreSQL
Redis
Nginx
Cloudflare API should be configurable through environment variables or mocked during development.
Production should NOT depend on Docker unless explicitly configured.
The primary installer should install native services:
PHP-FPM
PostgreSQL
Redis
Nginx
cloudflared
This is intended to behave like a traditional Linux hosting panel.
Use .env.
Example:
APP_NAME=CloudPanel
APP_ENV=production
APP_DEBUG=false
APP_URL=https://panel.example.com
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=cloudpanel
DB_USERNAME=cloudpanel
DB_PASSWORD=
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_API_TOKEN=
WEBSITE_ROOT=/srv/cloudpanel/websitesNever commit production secrets.
Provide:
.env.example
After installation:
CloudPanel installed successfully.
Panel:
https://panel.example.com
Admin:
admin@example.com
Do not print the admin password.
If generated automatically, require password change on first login.
Do not recommend blindly executing remote scripts in production.
Documentation should explain:
wget https://example.com/install.sh
less install.sh
chmod +x install.sh
sudo ./install.shThe installer should support checksum/version verification for future releases.
Provide:
cloudpanel uninstallThe uninstaller must ask for confirmation.
Example:
WARNING!
This will remove CloudPanel.
What should happen to hosted websites?
1. Keep website files
2. Delete website files
3. Cancel
Never silently delete user data.
The panel must remain recoverable if:
- Laravel crashes
- Nginx crashes
- PostgreSQL crashes
- Redis crashes
- Cloudflare API temporarily fails
- Cloudflared disconnects
Website files should remain independent from application files.
Therefore:
Panel failure
|
v
Website files remain
cloudpanel/
├── backend/
│ ├── app/
│ │ ├── Actions/
│ │ ├── Http/
│ │ ├── Jobs/
│ │ ├── Models/
│ │ ├── Policies/
│ │ ├── Services/
│ │ │ ├── Cloudflare/
│ │ │ ├── Deployment/
│ │ │ ├── Website/
│ │ │ └── Resource/
│ │ └── Console/
│ ├── database/
│ ├── routes/
│ └── tests/
│
├── frontend/
│ ├── src/
│ ├── components/
│ ├── pages/
│ └── services/
│
├── installer/
│ ├── install.sh
│ ├── uninstall.sh
│ ├── update.sh
│ ├── doctor.sh
│ └── common.sh
│
├── systemd/
│ ├── cloudpanel.service
│ ├── cloudpanel-worker.service
│ └── cloudpanel-scheduler.service
│
├── nginx/
│ └── cloudpanel.conf
│
├── scripts/
│ ├── backup.sh
│ └── restore.sh
│
├── .env.example
├── README.md
└── implementation.md
Implement in this order.
1. Laravel project
2. React frontend
3. PostgreSQL
4. Redis
5. Authentication
6. RBAC
7. User management
1. Website model
2. Website creation
3. Website storage
4. ZIP upload
5. Secure extraction
6. Nginx configuration generation
7. Deployment jobs
1. Cloudflare client
2. API token validation
3. Zone synchronization
4. DNS CRUD
5. Tunnel status
6. Website DNS automation
1. Storage calculation
2. User quotas
3. Website limits
4. Dashboard statistics
5. Server monitoring
1. install.sh
2. systemd services
3. Nginx configuration
4. cloudflared installation
5. PostgreSQL setup
6. Redis setup
7. Admin creation
8. Health checks
1. cloudpanel CLI
2. backup
3. restore
4. update
5. uninstall
6. doctor
7. audit logs
The implementation is considered complete only when:
- Admin can login
- Admin can create users
- Admin can suspend users
- Admin can configure quotas
- User can login
- User can create website
- User can upload ZIP
- Website deploys successfully
- Website is isolated from other users
- Nginx configuration is generated automatically
- Cloudflare zones can be synchronized
- User can only use allowed Cloudflare domains
- DNS CRUD works
- Cloudflare API token is never exposed
- Cloudflare Tunnel works
- Website becomes publicly accessible through Tunnel
- Resource usage is displayed
- Quotas are enforced
- Audit logs are recorded
- Unauthorized resource access is blocked
- ZIP path traversal is blocked
- Installer works on supported Debian 13 systems
- Installer is idempotent
- systemd services start automatically
-
cloudpanel doctorworks -
cloudpanel updateworks -
cloudpanel backupworks -
cloudpanel uninstallworks without silently deleting data - Application has automated tests
- Production documentation exists
The agent implementing this project MUST follow these principles:
- Do not expose Cloudflare credentials to users.
- Do not trust frontend authorization.
- Do not execute arbitrary shell commands from user input.
- Do not run user websites as root.
- Do not allow uploaded ZIP files to escape their assigned directory.
- Do not use the host root filesystem as user storage.
- Do not allow users to modify global Nginx configuration.
- Do not allow users to access other users' resources.
- Do not create one Cloudflare Tunnel per website in the initial version.
- Prefer one Tunnel + Nginx hostname routing.
- Use queues for long-running operations.
- Use database transactions where appropriate.
- Treat Cloudflare API operations as external side effects and implement retry/error handling.
- Never log secrets.
- Never silently delete user data.
- Keep website data independent from application data.
- Installer must be safe to run more than once.
- The production installer must support Debian 13.
- All security-sensitive operations must be auditable.
- All Cloudflare API operations must go through a dedicated service layer.
After installation:
https://panel.example.com
Admin logs in:
Dashboard
|
+-- Users
+-- Domains
+-- Websites
+-- Cloudflare
+-- Server
+-- Audit Logs
Admin creates:
User:
john@example.com
Websites:
5
Storage:
2 GB
DNS:
50 records
John logs in:
Dashboard
|
+-- Websites
+-- DNS
John creates:
Website:
portfolio
Domain:
example.com
Subdomain:
john
Uploads:
portfolio.zip
Panel automatically:
Validate quota
↓
Create website directory
↓
Extract ZIP safely
↓
Generate Nginx config
↓
Create Cloudflare DNS record
↓
Reload Nginx
↓
Verify website
Result:
https://john.example.com
No Cloudflare account is required for John.
No Cloudflare API token is exposed to John.
The final architecture should be:
INTERNET
|
v
+----------------+
| CLOUDFLARE |
| |
| DNS |
| Proxy |
| Tunnel |
+-------+--------+
|
CF Tunnel
|
v
+----------------------+
| SERVER |
| |
| +----------------+ |
| | NGINX | |
| +-------+--------+ |
| | |
| Host routing |
| | |
| +------+------+ |
| | | | |
| v v v |
| Web A Web B Web C |
| |
| +----------------+ |
| | Laravel Panel | |
| +-------+--------+ |
| | |
| +----+----+ |
| | | |
| v v |
| PostgreSQL Redis |
| |
| cloudflared |
+----------------------+
The panel is the control plane.
Cloudflare is the public network/DNS layer.
Nginx is the web routing layer.
The filesystem is the static hosting layer.
PostgreSQL is the state/database layer.
Redis is the queue/cache layer.
This separation should be maintained throughout the implementation.