Installing it yourself. Every step, nothing skipped.
Twenty minutes, most of it waiting on an upload. No terminal, no code — hosting-panel screens the whole way. Written against cPanel because that's what most hosting ships; Plesk, DirectAdmin and the rest have the same screens under different names (a file manager, a database page, a document-root setting, a cron page). Plain-VPS notes are marked where they differ.
No hosting yet? I recommend On Budget Services shared hosting — cPanel, PHP 8.3, and every screen in this walkthrough matches it, because it's what I run.
Every PHP site you've ever uploaded works like this: drop files in
public_html/something/ and yoursite.com/something/
serves them. ShiftWall does not work that way, and this is
the part that trips everybody up.
The folder you're about to upload contains about a dozen things —
app, config, vendor,
.env — and only one of them may be reachable from the
web: the folder called public. Everything else must
sit where a browser can't get to it, because .env holds your
database password in plain text.
/home/YOURNAME/ ← your hosting account's top level ├── public_html/ ← your main site, untouched └── shiftwall/ ← the app goes here, OUTSIDE public_html ├── app/ config/ vendor/ .env ← the web can't reach any of this └── public/ ← your subdomain points HERE
If your host won't let you work above public_html, there's
a safe fallback in the troubleshooting section.
cPanel → MultiPHP Manager → select your subdomain → choose PHP 8.3 or newer.
8.3 is the hard minimum. If your host's menu doesn't offer it, stop and ask them — nothing else in this guide works without it. (VPS: install php8.3-fpm and the extensions from the requirements list.)
cPanel → Domains → Create A New Domain:
schedule.yourstore.com — whatever you've chosen/home/YOURNAME/shiftwall/publicThat last field is the whole trick. cPanel will suggest
public_html/schedule — replace it with the path above. It's
fine that the folder doesn't exist yet.
Then SSL/TLS Status → tick the new subdomain → Run AutoSSL, and turn on Force HTTPS Redirect. Staff will type passwords into this site; it must be HTTPS.
VPS: an Nginx site block instead —
root /home/YOU/shiftwall/public;
index index.php;
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ { include fastcgi.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock; }
cPanel → MySQL Databases:
shiftwall;
cPanel makes it YOURNAME_shiftwallWrite down all three: database name, username, password. Both names get your account name stuck on the front — use the full versions, prefix and all.
cPanel → File Manager — easier than FTP here, because it can unzip:
/home/YOURNAME — the level
above public_htmlshiftwallOne zip rather than loose files is deliberate: the app is roughly ten thousand small files, and FTP uploads them one at a time — half an hour, and it silently drops a few. One zip takes about a minute. If you must use FTP, upload the zip by FTP and still use File Manager for the extract.
The app needs a settings file that is deliberately not in the zip,
because it will hold live passwords. In File Manager, inside
shiftwall, find .env.example. (Can't see files
starting with a dot? Settings, top right → tick
Show Hidden Files.)
Right-click it → Copy → name the copy .env.
Then right-click .env → Edit, and set these
lines:
APP_NAME="Your Store Name" APP_ENV=production APP_DEBUG=false APP_URL=https://schedule.yourstore.com APP_TIMEZONE=America/Chicago DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_DATABASE=YOURNAME_shiftwall DB_USERNAME=YOURNAME_shiftwalluser DB_PASSWORD=the-password-you-copied SESSION_SECURE_COOKIE=true INSTALL_TOKEN=paste-something-long-and-random-here
APP_DEBUG=false is not optional — left on, errors show
visitors your file paths.APP_URL must be the real https address; every link in
every emailed schedule is built from it.APP_TIMEZONE: your store's zone, like
America/New_York. The setup walk-through asks again later
and its answer wins.INSTALL_TOKEN is a one-time password for the next step.
Mash the keyboard — thirty or so random characters. While this line is
blank, the setup page does not exist at all.DB_CONNECTION=sqlite line further up — put a
# in front of it, and make sure the mysql lines have no
#.APP_KEY — the setup page
generates it.Save.
Go to:
https://schedule.yourstore.com/install/YOUR-INSTALL-TOKEN
— the last part being the exact random characters from your
.env. You'll see a small page titled "Set up the schedule."
It asks two things: the owner's name and the email they'll sign in with.
Press Set it up, and in one go it generates the app's
secret key, creates every database table, and creates the owner's
account.
It deliberately creates no shifts and no jobs — your store isn't anybody else's. The first sign-in walks the owner through defining your own, in plain words, in about ten minutes.
Then go back to .env and delete the
INSTALL_TOKEN line. The page already refuses to run twice, but
there's no reason to leave the key in the door.
.env and your panel disagree. Check both names carry your
account prefix, the user was added with all privileges, and
DB_HOST is 127.0.0.1.
Either the token in the address doesn't exactly match the one in
.env, or an account already exists — setup already ran, and
you should just go to /login.
Publishing a schedule queues the messages; a background task actually sends them. Without it, publishing looks fine and nothing ever arrives.
cPanel → Cron Jobs → Add New Cron Job → Common Settings: Once Per Minute. Add both:
# sends the messages cd /home/YOURNAME/shiftwall && /usr/local/bin/php artisan queue:work --stop-when-empty --max-time=55 >/dev/null 2>&1 # scheduled housekeeping cd /home/YOURNAME/shiftwall && /usr/local/bin/php artisan schedule:run >/dev/null 2>&1
If messages still don't send, the PHP path differs on your host — on
many cPanel servers it's
/opt/cpanel/ea-php83/root/usr/bin/php. Swap that in.
Schedules go out by email, so the app needs a mailbox to send from.
cPanel → Email Accounts → create something like
schedule@yourstore.com, then add to .env:
MAIL_MAILER=smtp
MAIL_HOST=mail.yourstore.com
MAIL_PORT=465
MAIL_USERNAME=schedule@yourstore.com
MAIL_PASSWORD=the-mailbox-password
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=schedule@yourstore.com
MAIL_FROM_NAME="Your Store Name"
Test it by publishing a week to yourself before adding real staff. If it lands in spam: cPanel → Email Deliverability → fix SPF and DKIM. Usually one button each.
Go to your new address, sign in with the email and one-time password from step 6, and do two things right away: change that password (Settings → Security) and turn on two-factor sign-in while you're there.
ShiftWall then greets you with its setup walk-through — your store's name, your jobs, your shifts, your people. From there the guide takes over.
APP_KEY. Put your
INSTALL_TOKEN line back in .env and visit the
install address again — on a site that already has accounts, that page
does exactly one thing: regenerates the key, touching no data. Delete
the token line after. If the key is fine, set storage and
bootstrap/cache permissions to 755
recursively; the real error is at the bottom of
storage/logs/laravel.log.public folder inside the app —
/home/YOURNAME/shiftwall/public — not the app folder
itself and not public_html.APP_URL doesn't match the address in your browser,
exactly, https and all. Fix it; if nothing changes, also delete
bootstrap/cache/config.php — a cached copy that wins over
the file.public/build folder didn't survive the upload, or
APP_URL is wrong. Re-extract the zip and check both.bootstrap/cache/config.php if it exists.INSTALL_TOKEN back to .env, open your
database's users table in phpMyAdmin and delete the
owner's row, then visit the install address again — it makes a fresh
owner account and shows a new password. Everything else stays put.
Delete the token line when done.public_html/shiftwall and point the
subdomain's document root at
…/public_html/shiftwall/public. That works, defended by
the shipped .htaccess — but prefer the layout at the top
when your host allows it. cPanel's File Manager can always go above
public_html, even when FTP can't..env alone. If an update needs
database steps, its release notes say so.