Automatically renews PythonAnywhere free-tier web apps and scheduled tasks every 15 days via GitHub Actions β supports one account or many, no legacy variables required.
- Overview
- Features
- Tech Stack
- Architecture
- Getting Started
- Project Structure
- Configuration
- Account Configuration Examples
- Workflow Logs
- Troubleshooting
- Contributing
- License
PythonAnywhere free tier apps expire after 3 months 1 month of inactivity (updated Jan 2026). This bot automatically renews your web app by logging into PythonAnywhere and clicking the "Extend" button every 15 days via GitHub Actions.
Day 1: Deploy app β
Day 30: App expires β
Day 31: Users see error page π’
Day 32: Manual renewal... again π€
Day 1: Deploy app β
Day 15: Bot auto-renews β
Day 30: Bot auto-renews β
...forever! π
- Scheduled renewal on the 1st and 15th of every month (04:00 UTC / 09:30 IST)
- Manual trigger available from GitHub Actions tab
- Automatic log commits prevent GitHub from disabling the workflow
- Renews every configured account independently with a separate login session
- Complete audit trail in
.github/logs/workflow_runs.log - Clear SUCCESS, PARTIAL, or FAILED status indicators
- Timestamps, run IDs, and trigger source for debugging
- Detailed per-account results showing exactly which items were renewed and their old vs. new expiry dates
- Explicit reporting for incomplete credentials, duplicate accounts, skipped items, and request failures
- Credentials stored as encrypted GitHub Secrets or a local
.envfile - Passwords are never written to the committed audit log
- GitHub Actions masks configured usernames and passwords in the live console
- HTTPS for all PythonAnywhere connections
| Layer | Technologies |
|---|---|
| Language | Python 3.9+ |
| Libraries | requests, beautifulsoup4, python-dotenv |
| CI/CD | GitHub Actions |
| Target | PythonAnywhere Free Tier |
flowchart LR
trigger["1. Trigger<br/>Scheduled or manual run"]
prepare["2. Prepare<br/>Checkout, install dependencies,<br/>load and validate credentials"]
renew["3. Renew accounts<br/>One isolated session per account;<br/>authenticate, extend web apps and tasks,<br/>verify new expiry dates"]
platform["4. PythonAnywhere<br/>Web app dashboard and<br/>scheduled-task API"]
record["5. Record outcome<br/>Aggregate SUCCESS, PARTIAL, or FAILED;<br/>write and commit workflow_runs.log"]
trigger --> prepare --> renew
renew <--> platform
renew --> record
- GitHub Actions runs the workflow on the 1st and 15th of each month.
- The credential resolver discovers complete
PA_*, numbered, and JSON account configurations, then reports incomplete or duplicate entries. - The orchestrator creates an isolated authenticated session for each account.
- The web app and scheduled task adapters renew the resources exposed by PythonAnywhere.
- Results are written to
.github/logs/workflow_runs.logwith an explicitSUCCESS,PARTIAL, orFAILEDstatus. - The workflow commits the log so the repository retains an auditable run history. A failed account does not prevent later configured accounts from being attempted; the aggregate status records the failure.
- PythonAnywhere free account with a web app or scheduled task
- GitHub account
- 5 minutes of setup time
git clone https://github.com/tanishqmudaliar/PythonAnywhere-Auto-Renew.git
cd PythonAnywhere-Auto-RenewOr click "Use this template" on GitHub.
- Go to your repository β Settings β Secrets and variables β Actions
- Click New repository secret and add:
| Secret Name | Value |
|---|---|
PA_USERNAME |
Optional legacy account username |
PA_PASSWORD |
Optional legacy account password |
For multiple accounts, add numbered secret pairs such as
ACCOUNT_1_USERNAME and ACCOUNT_1_PASSWORD. Numbered pairs do not need to be
consecutive. The PA_* pair is optional and can be used together with numbered
pairs or omitted entirely.
The workflow exposes numbered pairs 1 through 10. For more than ten accounts,
or to manage all accounts in one secret, use ACCOUNT_CREDENTIALS_JSON:
[
{ "username": "account_one", "password": "password_one" },
{ "username": "account_two", "password": "password_two" }
]- Go to Settings β Actions β General
- Under Workflow permissions, select "Read and write permissions"
- Check "Allow GitHub Actions to create and approve pull requests"
- Click Save
- Go to the Actions tab
- Click Auto-Renew PythonAnywhere
- Click Run workflow β Run workflow
- Verify the run shows each configured account and creates a detailed entry in
.github/logs/workflow_runs.log.
-
Install dependencies
pip install -r requirements.txt
-
Create a
.envfilePA_USERNAME=your_username PA_PASSWORD=your_password
The legacy pair is optional. Numbered local variables also work:
ACCOUNT_1_USERNAME=first_username ACCOUNT_1_PASSWORD=first_password ACCOUNT_3_USERNAME=third_username ACCOUNT_3_PASSWORD=third_password
Or use JSON for any number of accounts:
ACCOUNT_CREDENTIALS_JSON=[{"username":"first_username","password":"first_password"}]
-
Run the script
python renew_python_anywhere.py
The script writes the detailed result to .github/logs/workflow_runs.log.
The terminal output includes one processing section per complete account:
π Logging in as your_username...
β
Login successful
π Checking web apps...
ποΈ Checking scheduled tasks...
PythonAnywhere-Auto-Renew/
βββ .github/
β βββ workflows/
β β βββ renew.yml # GitHub Actions workflow
β βββ logs/
β βββ workflow_runs.log # Auto-generated run history
βββ renew_python_anywhere.py # Main renewal script
βββ requirements.txt # Python dependencies
βββ .env # Local credentials (not in git)
βββ .gitignore # Git ignore rules
βββ LICENSE # MIT License
βββ README.md # Project documentation
PA_USERNAME=main_username
PA_PASSWORD=main_passwordACCOUNT_1_USERNAME=first_username
ACCOUNT_1_PASSWORD=first_password
ACCOUNT_3_USERNAME=third_username
ACCOUNT_3_PASSWORD=third_passwordAccount 2 may be absent. The script still discovers account 3.
PA_USERNAME=main_username
PA_PASSWORD=main_password
ACCOUNT_1_USERNAME=first_username
ACCOUNT_1_PASSWORD=first_passwordAll complete unique accounts are processed. If the same credentials are configured twice, the duplicate is skipped and recorded as a warning.
ACCOUNT_2_USERNAME=missing_passwordThe account is not processed and the run is marked PARTIAL if at least one
other complete account runs successfully.
[
{ "username": "first_username", "password": "first_password" },
{ "username": "second_username", "password": "second_password" }
]Store the JSON as the value of ACCOUNT_CREDENTIALS_JSON. Invalid JSON or
incomplete entries are reported in the log.
| Variable | Description | Required |
|---|---|---|
PA_USERNAME |
Optional PythonAnywhere username | No |
PA_PASSWORD |
Optional PythonAnywhere password | No |
ACCOUNT_N_USERNAME |
Optional numbered account username | No |
ACCOUNT_N_PASSWORD |
Optional numbered account password | No |
ACCOUNT_CREDENTIALS_JSON |
Optional JSON list or object of accounts | No |
Edit .github/workflows/renew.yml to change the cron schedule:
# Current: 1st and 15th at 04:00 UTC
- cron: "0 4 1,15 * *"
# Alternative: Every Monday at noon UTC
- cron: "0 12 * * 1"
# Alternative: Every 10 days
- cron: "0 4 1,11,21 * *"Every run is logged to .github/logs/workflow_runs.log:
Every run appends one detailed block to .github/logs/workflow_runs.log.
SUCCESS means every configured account completed. PARTIAL means the
renewals completed but at least one credential pair was incomplete.
FAILED means at least one account renewal failed or no complete account was
available.
========================================
Workflow Run: 2026-07-15 04:00:00 UTC
Status: SUCCESS
Trigger: schedule
Repository: username/PythonAnywhere-Auto-Renew
Branch: main
Run ID: 123456789
Configured accounts:
- PA: mainuser
- ACCOUNT_1: accountone
Renewal details:
- PA: SUCCESS
- Web App: mainuser.pythonanywhere.com (old date β new date)
- ACCOUNT_1: SUCCESS
- Task: python3 sync.py (Already maxed out at: 2026-08-11)
========================================
========================================
Workflow Run: 2026-07-15 04:00:00 UTC
Status: PARTIAL
Trigger: schedule
Repository: username/PythonAnywhere-Auto-Renew
Branch: main
Run ID: 123456790
Configured accounts:
- PA: mainuser
Incomplete or skipped credentials:
- ACCOUNT_2_USERNAME/ACCOUNT_2_PASSWORD
Renewal details:
- PA: SUCCESS
- Web App: mainuser.pythonanywhere.com (old date β new date)
========================================
========================================
Workflow Run: 2026-07-15 04:00:00 UTC
Status: FAILED
Trigger: schedule
Repository: username/PythonAnywhere-Auto-Renew
Branch: main
Run ID: 123456790
Configured accounts:
- PA: mainuser
- ACCOUNT_1: accountone
Renewal details:
- PA: SUCCESS
- Web App: mainuser.pythonanywhere.com (old date β new date)
- ACCOUNT_1: FAILED
- Login failed
========================================
- Verify the relevant
PA_*,ACCOUNT_N_*, or JSON credentials are correct - Try logging in manually to confirm credentials work
- Check if PythonAnywhere changed their login page
- Add either a complete
PA_USERNAMEandPA_PASSWORDpair. - Add at least one complete numbered pair.
- Add a valid
ACCOUNT_CREDENTIALS_JSONlist or object. - Empty numbered slots are ignored. Partially populated slots are reported as incomplete.
- This is normal, your app doesn't need renewal yet
- The renewal form only appears when renewal is due
- Logged as SUCCESS β (not an error)
- PythonAnywhere caps scheduled task extensions (usually a maximum of 30 days into the future).
- If you run the bot frequently or trigger it manually, a task might already be at its maximum expiry limit.
- The bot recognizes this and safely logs it as a SUCCESS β without failing your workflow.
- GitHub disables inactive workflows after 60 days
- This bot commits logs every 15 days to prevent this
- Re-enable manually if needed, then run the workflow
- Check the GitHub Actions workflow run for detailed error logs
- Common causes: wrong credentials, PythonAnywhere site changes
- Workflow retries automatically on the next scheduled run
Contributions are welcome! Please feel free to submit pull requests or open issues.
- Fork the repository
- Create a feature branch (
git checkout -b feature/improvement) - Commit your changes (
git commit -m 'Add improvement') - Push to the branch (
git push origin feature/improvement) - Open a Pull Request
This project is open source and available under the MIT License.
Made with β€οΈ by Tanishq Mudaliar
Stop manually clicking that extend button. Automate it! π