Overview
Envloom uses Nginx as the web server for all PHP sites with automatic configuration generation, local SSL certificate management, per-site logging, and hot-reload capabilities. Nginx is installed automatically during bootstrap.
Installation
Automatic Bootstrap
Nginx downloads and configures automatically on first launch:
Fetch Latest Release
Queries GitHub API for latest nginx/nginx release
Download ZIP
Downloads the Windows ZIP (e.g., nginx-1.25.3.zip)
Extract and Configure
Extracts to bin/nginx/<version>/ and flattens nested directory structure
Set Current Link
Creates junction at bin/nginx/current pointing to installed version
Configure Sites Include
Modifies conf/nginx.conf to include site configs from sites/*.conf
Nginx installation is non-blocking. The app continues loading while Nginx downloads in the background.
Version Detection
Installed Nginx version is detected by:
Scanning bin/nginx/ for directories with nginx.exe
Running nginx.exe -v to extract version string
Parsing output like nginx version: nginx/1.25.3
fn detect_nginx_version_from_binary ( nginx_root : & PathBuf ) -> Option < String > {
let nginx_exe = nginx_root . join ( "current" ) . join ( "nginx.exe" );
let output = Command :: new ( & nginx_exe ) . arg ( "-v" ) . output () . ok () ? ;
// Parse version from stderr output
}
Manual Installation
If auto-install fails, manually place Nginx in bin/nginx/<version>/:
# Download from nginx.org
curl -O https://nginx.org/download/nginx-1.25.3.zip
# Extract
unzip nginx-1.25.3.zip
# Move to Envloom
move nginx-1.25.3 C: \p ath \t o \e nvloom \b in \n ginx \1 .25.3
# Create junction
mklink /J C: \p ath \t o \e nvloom \b in \n ginx \c urrent C: \p ath \t o \e nvloom \b in \n ginx \1 .25.3
Configuration
Main Configuration (nginx.conf)
Envloom modifies the default nginx.conf to include site configs:
http {
# ... default config ...
# Envloom global logs
error_log C:/path/to/envloom/logs/nginx/error.log;
access_log C:/path/to/envloom/logs/nginx/access.log;
# Include all site configs
include C:/path/to/envloom/sites/*.conf;
}
This injection happens automatically via ensure_nginx_sites_include().
Site Configuration Generation
Each site gets its own config file in sites/<domain>.conf:
server {
listen 80 ;
server_name mysite.test;
access_log C:/path/to/logs/nginx/sites/mysite.test.access.log;
error_log C:/path/to/logs/nginx/sites/mysite.test.error.log;
root C:/path/to/mysite/public;
index index.php index.html index.htm;
location / {
try_files $ uri $ uri / /index.php?$ query_string ;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $ document_root $ fastcgi_script_name ;
fastcgi_pass 127.0.0.1:9083; # PHP 8.3 FPM port
}
}
Path Normalization
Windows paths are converted to Nginx format:
fn to_nginx_path ( path : & Path ) -> String {
path . to_string_lossy () . replace ( ' \\ ' , "/" )
}
// Example:
// C:\Users\Dev\envloom\sites\mysite
// → C:/Users/Dev/envloom/sites/mysite
PHP-FPM Integration
Each site config references the PHP-FPM port for its assigned PHP version:
let php_fpm_port = line_port ( php_config . base_port, & site . php_version);
// base_port: 9000
// PHP 8.3 → 9083
// PHP 8.2 → 9082
write_nginx_site_config (
& nginx_root ,
& sites_dir ,
& site . domain,
& site . path,
php_fpm_port , // Injected into fastcgi_pass
site . ssl_enabled
);
Service Management
Starting Nginx
Nginx starts automatically if autoStartServices is enabled:
Start-Process - WindowStyle Hidden `
- FilePath "bin/nginx/current/nginx.exe" `
- WorkingDirectory "bin/nginx/current"
Nginx must start from its own directory (-p flag or working directory) to correctly resolve relative paths in nginx.conf.
Stopping Nginx
# Graceful shutdown
nginx.exe -s quit
# Fast shutdown
nginx.exe -s stop
# Via PowerShell (Envloom method)
Get-Process -Name 'nginx' |
Where-Object { $_ .Path -like 'bin/nginx/*' } |
Stop-Process -Force
Envloom stops Nginx automatically on app exit.
Reloading Configuration
Reload Nginx without downtime after config changes:
# Via CLI (future)
loom reload
# Direct Nginx command
nginx.exe -s reload
# Via Envloom API
await reloadNginx ();
Configuration Test
Automatic Reload
Always test config before reload: Envloom automatically tests config before starting or reloading. Envloom reloads Nginx automatically when:
New site is created
Site SSL is toggled
Site PHP version changes
Site is unlinked/deleted
Process Detection
fn is_process_running ( name : & str ) -> bool {
// Checks Windows task list for nginx.exe
}
if ! is_process_running ( "nginx.exe" ) {
start_nginx_if_needed ( & nginx_root ) ? ;
}
SSL Certificate Management
Local Certificate Authority
Envloom creates a persistent local CA for signing site certificates:
// CA stored at sites/ca/
sites /
├── ca /
│ ├── ca . crt # CA certificate ( trust this in browser )
│ ├── ca . key # CA private key
│ └── ca . der # CA certificate ( DER format )
├── certs /
│ ├── mysite . test . crt
│ ├── mysite . test . key
│ └── ...
└── *. conf
Generating Site Certificates
Certificates are generated using the rcgen crate:
Load or Create CA
If CA doesn’t exist, generate new RSA 4096-bit CA with 10-year validity
Generate Site Keypair
Create RSA 2048-bit keypair for the site domain
Sign with CA
Sign site certificate with CA, valid for 825 days
Write Files
Save <domain>.crt and <domain>.key to sites/certs/
fn ensure_site_ssl_cert (
sites_dir : & Path ,
domain : & str
) -> Result <( PathBuf , PathBuf ), String > {
let ca = ensure_local_ca ( sites_dir ) ? ;
let cert_dir = sites_dir . join ( "certs" );
let crt_path = cert_dir . join ( format! ( "{domain}.crt" ));
let key_path = cert_dir . join ( format! ( "{domain}.key" ));
if crt_path . exists () && key_path . exists () {
return Ok (( crt_path , key_path ));
}
// Generate new certificate signed by CA
let site_cert = generate_site_certificate ( domain , & ca ) ? ;
write_pem ( & crt_path , & site_cert . certificate) ? ;
write_pem ( & key_path , & site_cert . private_key) ? ;
Ok (( crt_path , key_path ))
}
Trusting the CA
To eliminate browser warnings, trust the CA certificate:
Windows
Chrome/Edge
Firefox
# Import CA to Trusted Root Certification Authorities
certutil - addstore - user Root sites\ca\ca.crt
# Or via UI
# Double-click ca.crt > Install Certificate > Current User >
# Place in: Trusted Root Certification Authorities
Uses Windows certificate store automatically after certutil import.
Firefox uses its own certificate store:
Open Settings > Privacy & Security > Certificates > View Certificates
Authorities tab > Import
Select sites/ca/ca.crt
Trust for identifying websites
SSL Toggle Per Site
// Enable SSL for a site
await toggleSiteSSL ( siteId , true );
// Regenerates nginx config with:
// - HTTP redirect to HTTPS
// - HTTPS server block with ssl_certificate directives
// - Reloads nginx
Bulk SSL Operations
From systray menu:
// Enable SSL for all sites
TRAY_MENU_SSL_ALL_ON => {
for site in sites {
site . ssl_enabled = true ;
regenerate_nginx_config ( & site );
}
reload_nginx ();
}
// Disable SSL for all sites
TRAY_MENU_SSL_ALL_OFF => {
for site in sites {
site . ssl_enabled = false ;
regenerate_nginx_config ( & site );
}
reload_nginx ();
}
Logging
Global Logs
logs/nginx/
├── access.log # All HTTP requests across all sites
├── error.log # Nginx startup errors and warnings
└── sites/
├── mysite.test.access.log
├── mysite.test.error.log
└── ...
Per-Site Logs
Each site gets dedicated access and error logs:
server {
access_log C:/path/to/logs/nginx/sites/mysite.test.access.log;
error_log C:/path/to/logs/nginx/sites/mysite.test.error.log;
# ...
}
Log Viewing
Navigate to Logs page > Nginx tab:
General selector : Global access/error logs
Per-site selector : Individual site logs
Uses @melloware/react-logviewer for syntax highlighting
# Via tail (future feature)
loom logs nginx
# Manual
type logs \n ginx \a ccess.log
Get-Content logs \n ginx \e rror.log -Tail 50 -Wait
Log Reconciliation
Orphan log files are cleaned up automatically:
fn reconcile_nginx_site_configs ( sites_dir : & Path , domains : & [ String ]) {
// Remove *.conf for deleted sites
// Remove *.access.log and *.error.log for deleted sites
}
Hosts File Management
Envloom manages the Windows hosts file for local domains:
Hosts Block Structure
# Envloom generated Hosts - do not edit manually
127.0.0.1 mysite.test
127.0.0.1 another.test
127.0.0.1 project.test
# End Envloom Hosts
Envloom owns entries within its block. Manual edits will be overwritten. For custom domains, add them outside the Envloom block.
Automatic Updates
Hosts file updates automatically when:
New site is created
Site domain changes
Site is unlinked/deleted
Elevated Permissions
Modifying hosts requires admin rights. Envloom handles this via UAC:
// Attempt direct write
let result = fs :: write ( & hosts_path , new_content );
if result . is_err () {
// Elevate and retry via PowerShell
let script = format! (
"Start-Process -Verb RunAs powershell -ArgumentList \
'-NoProfile -Command \" Set-Content -Path {} -Value ... \" "
);
run_powershell ( & script ) ? ;
}
Hosts Reconciliation
Envloom reconciles the hosts file on startup:
// Move orphan Envloom domains into the managed block
// Remove domains for deleted sites
// Preserve non-Envloom entries (e.g., Herd sites)
Envloom preserves hosts entries from other tools like Laravel Herd by using separate comment blocks.
Configuration Reconciliation
Envloom maintains consistency between sites and Nginx configs:
On Startup
// Scan sites directory for *.conf files
// Compare against registered sites
// Remove configs for unregistered sites
// Regenerate configs for sites with missing *.conf
On Site Operations
Create site : Generate new <domain>.conf
Update PHP version : Regenerate config with new FPM port
Toggle SSL : Regenerate config with/without HTTPS
Unlink site : Remove <domain>.conf
Orphan Cleanup
fn reconcile_nginx_site_configs ( sites_dir : & Path , domains : & [ String ]) {
let managed : HashSet < String > = domains . iter ()
. map ( | d | d . trim () . to_lowercase ())
. collect ();
for entry in fs :: read_dir ( sites_dir ) ? {
if entry . extension () == "conf" {
let domain = entry . file_stem () ? ;
if ! managed . contains ( & domain ) {
fs :: remove_file ( entry ) ? ; // Orphan
}
}
}
}
CLI Commands
Direct Nginx Commands
# Test configuration
nginx -t
# Start (if not running)
nginx
# Stop gracefully
nginx -s quit
# Stop immediately
nginx -s stop
# Reload config
nginx -s reload
# Reopen log files
nginx -s reopen
Envloom CLI (Future)
# Reload all services
loom reload
# View nginx logs
loom logs nginx
# Open config directory
loom open nginx configs
Troubleshooting
Nginx Won’t Start
Check error log first:
type logs \n ginx \e rror.log
Port 80/443 Already in Use
Find conflicting process: netstat -ano | findstr :80
netstat -ano | findstr :443
# Kill process (replace PID)
taskkill /PID 1234 /F
Common conflicts: IIS, Apache, Skype
Configuration Syntax Error
Test config: Fix reported errors in nginx.conf or site configs.
Run Envloom as administrator or check file permissions: icacls bin \n ginx /grant %USERNAME%:F /T
502 Bad Gateway
PHP-FPM is not running or port mismatch:
# Check PHP-FPM is running
netstat -ano | findstr :9083
# Check site config fastcgi_pass matches PHP version port
type sites \m ysite.test.conf | findstr fastcgi_pass
# Restart PHP-FPM
loom reload
SSL Certificate Errors
Browser shows “Not Secure” warning:
Check Certificate Files Exist
dir sites \c erts \m ysite.test. *
Regenerate Certificate
await regenerateSiteSSL ( siteId );
Trust CA Certificate
certutil - addstore - user Root sites\ca\ca.crt
Clear Browser Cache
Restart browser to reload certificate trust store
Site Not Resolving
Check hosts file:
type C: \W indows \S ystem32 \d rivers \e tc \h osts
Ensure domain is in Envloom block:
If missing, reconcile from Envloom dashboard.
Best Practices
Configuration Management
Don’t edit generated site configs manually - use Envloom UI
Custom nginx directives: Add to main nginx.conf outside site includes
Keep nginx.conf backed up before major changes
# Add to nginx.conf http block
worker_processes auto;
worker_connections 1024 ;
keepalive_timeout 65 ;
gzip on ;
gzip_types text/plain text/css application/json application/javascript;
Security
# Hide nginx version
server_tokens off ;
# Add security headers
add_header X-Frame-Options "SAMEORIGIN" ;
add_header X-Content-Type-Options "nosniff" ;
add_header X-XSS-Protection "1; mode=block" ;
Logging
# Custom log format with timing
log_format timing '$ remote_addr - $ remote_user [$ time_local ] '
'"$ request " $ status $ body_bytes_sent '
'"$ http_referer " "$ http_user_agent " '
'rt=$ request_time uct=$ upstream_connect_time '
'uht=$ upstream_header_time urt=$ upstream_response_time ' ;
access_log logs/nginx/access.log timing;