Verdict at the top: OWASP publishes a Top 10 list of vulnerability categories. In the incidents we see on shared hosting, three of those ten cause most of the damage: out-of-date WordPress plugins/themes (Vulnerable Components), credential stuffing on weak admin passwords (Authentication Failures), and unrestricted file uploads that get used to deploy phishing kits (Broken Access Control + Security Misconfiguration). The other seven matter, but if you fix the three above before anything else, you've eliminated most of the practical attack surface against your site. This guide orders the Top 10 by what we see hit hosting accounts in practice, with concrete defences in PHP and Node.js and the typical incidents that explain why each one matters.
The OWASP Top 10 is a list, but the items aren't equally common. The top three are stale WordPress, weak admin passwords, and unrestricted file upload. Phishing kits, casino-spam SEO injection, and outbound spam floods are the three incident shapes we see most. Lockdown order: update everything, rotate passwords + add MFA, block file uploads outside an allow-listed type set, then work down the rest of the list.
How we ranked these — ground-truth from our hosting servers
The ranking below isn't OWASP's official ordering. It's a rough order of the incident types we see on hosting accounts. The category codes (A01–A10) are from the 2021 edition, which most tools and articles still reference.
OWASP published a 2025 edition of the Top 10. It keeps most of these risks but renames and reorders some: for example, vulnerable components are broadened into software supply chain failures, and SSRF is folded into broken access control. The defences in this guide apply to both editions; check owasp.org for the current list and codes.
| Rank | OWASP category | How we see it manifest |
|---|---|---|
| 1 | A06 Vulnerable Components | Outdated WordPress plugins/themes → drive-by RCE → site defaced or used as phishing host |
| 2 | A07 Auth Failures | Weak/reused admin password → credential stuffing → admin takeover |
| 3 | A01 Broken Access Control | Unrestricted file upload → attacker uploads PHP shell → deploys phishing kit |
| 4 | A03 Injection | SQL injection on a homemade form → DB dump or admin escalation |
| 5 | A05 Security Misconfiguration | Default admin/admin, exposed .git, verbose error pages |
| 6 | A09 Logging Failures | Customer notices something is wrong only when Google flags the site |
| 7 | A02 Cryptographic Failures | MD5 password hashes, plain HTTP login, secrets in git |
| 8 | A04 Insecure Design | OTP without rate limit, password-reset that emails the password |
| 9 | A10 SSRF | Image-fetch features that proxy arbitrary URLs |
| 10 | A08 Integrity Failures | Unsigned auto-updates, missing SRI on CDN scripts |
If you have a budget of one weekend, fix items 1, 2, and 3. The rest can be sequenced over the following weeks.
A06 — Vulnerable Components (the #1 incident shape)
What we see in practice. A customer installs WordPress, picks a free theme, installs 15 plugins, then forgets. Six months later, a known CVE in one of those plugins is exploited by an automated scanner. Within hours the attacker drops a PHP webshell into /wp-content/uploads/ or /wp-content/themes/your-theme/. From there: SEO spam injection, phishing kit deployment, or use of the host as an outbound-spam relay.
A typical incident: a phishing kit appears on several domains under one hosting account, cloning a bank's login page and sending victims' credentials to the attacker. It is usually discovered through abuse reports from the targeted bank or a network abuse desk, and the shared IP address can end up on spam blocklists. The root cause is a stale WordPress plugin with a known unauthenticated upload vulnerability, often in a forgotten install: a one-page WordPress site a previous developer set up years earlier that the owner no longer knew existed.
Defence — automate it, you won't remember to do it manually:
# Node.js
npm audit
npm audit fix
# PHP / Composer
composer audit
# WordPress (via WP-CLI)
wp plugin update --all
wp theme update --all
wp core update
# Container scanning
trivy image your-app:latestRun on every CI build. Block merge on high-severity findings. For WordPress sites without CI, set a weekly cron to email you a wp plugin status snapshot.
GitHub's Dependabot and self-hosted Renovate auto-create PRs for dependency updates — turn them on for every repo you maintain. If you've inherited a project whose package files don't lock versions, the first task is to lock them.
The Domain India angle. Our cPanel and DirectAdmin servers run Imunify360, which scans for known malware; on cPanel it cleans detected files automatically. Webuzo servers run ImunifyAV. Customers can't start these scans themselves, and none of it is a substitute for keeping plugins updated.
A07 — Identification & Authentication Failures (#2)
What we see in practice. Brute-force login attempts hammer cPanel /login, WordPress /wp-login.php, and webmail at all hours. The server firewall blocks IP addresses that fail too often, but the attacks themselves are constant. The successful breaches are usually credential stuffing — username/password combos leaked from another breach, replayed against your login.
IP-based blocking has a limit: attackers rotate through many IP addresses, so each one stays under the threshold. That is why the account-level defences below matter.
Defences in priority order:
- Strong unique passwords for every admin account. Generated, not chosen. Stored in a password manager (1Password, Bitwarden, KeePassXC). Never reused.
- Multi-factor authentication everywhere it's available. WordPress: WP 2FA plugin or Wordfence Login Security. cPanel: enable 2FA from cPanel → Two-Factor Authentication. Custom apps: TOTP via
otplib(Node) orspomky-labs/otphp(PHP). Example with the otplib v12 API (check the docs for the version you install):javascript import { authenticator } from 'otplib'; authenticator.options = { window: 1 }; // accept ±30s drift // On user enrolment const secret = authenticator.generateSecret(); const otpauthUrl = authenticator.keyuri(user.email, 'My App', secret); // Save secret (encrypted) to the user record, show otpauthUrl as a QR code // On login verification const valid = authenticator.verify({ token: req.body.code, secret: user.mfaSecret }); - Rate limit the login endpoint — both successful and failed attempts:
javascript import rateLimit from 'express-rate-limit'; app.post('/login', rateLimit({ windowMs: 15 * 60 * 1000, limit: 10, // 'max' in older versions message: { error: 'Too many attempts, try again in 15 minutes' }, standardHeaders: true, }), loginHandler); - Rotate the session ID on login to prevent session fixation:
php session_regenerate_id(true); // PHP, after successful loginjavascript req.session.regenerate(() => { /* set req.session.userId */ }); // Express - Lock accounts after N failed attempts. Five is a reasonable default for end users; three for admin accounts. Auto-unlock after 30 minutes; permanent lock after a higher threshold to prevent casual brute-force.
- Don't reveal which inputs were wrong. "Invalid email or password" — never "no account with that email" or "password incorrect for this account". The latter helps account-enumeration attacks.
A01 — Broken Access Control (#3 by frequency, but huge by impact)
What we see in practice. Two patterns dominate:
Pattern 1: Insecure Direct Object References (IDOR). GET /api/orders/42 returns order 42 to any authenticated user, not just the owner. Same for /api/invoices/, /api/users/, document downloads — anywhere the URL contains an ID and the server doesn't verify ownership. Trivially exploited by changing the number.
Pattern 2: Unrestricted file upload. Custom contact form or media uploader accepts any file extension without checking. Attacker uploads evil.php to a public path, opens it in a browser, gets remote code execution. From there, phishing kits, SEO spam, outbound mail abuse — all the incidents in this list.
Defence (Node.js IDOR):
app.get('/api/orders/:id', authenticate, async (req, res) => {
const order = await db.order.findUnique({ where: { id: req.params.id } });
if (!order) return res.status(404).send('Not found');
// Critical: verify ownership
if (order.userId !== req.user.id) {
return res.status(404).send('Not found'); // 404, not 403 — don't confirm existence
}
res.json(order);
});Rule: every resource query must filter by userId = session.userId or include an explicit role check. Don't rely on URLs or admin-menu placement.
Defence (file upload, PHP):
$allowedMimes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
$allowedExts = ['jpg', 'jpeg', 'png', 'webp', 'pdf'];
$file = $_FILES['upload'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$mime = mime_content_type($file['tmp_name']);
if (!in_array($ext, $allowedExts, true) || !in_array($mime, $allowedMimes, true)) {
http_response_code(400);
exit('Invalid file type');
}
// Generate a safe filename — never trust user-supplied name
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
// Store outside the document root, OR ensure PHP can't execute in the upload dir
move_uploaded_file($file['tmp_name'], '/home/user/private-uploads/' . $safeName);For storage location, prefer outside the public document root entirely — serve files via a script that streams them with proper Content-Type. If they must live in a public path, drop a .htaccess to disable PHP execution:
# In the upload directory
<FilesMatch "\.(php|phtml|phar|inc|cgi)$">
Require all denied
</FilesMatch>This single .htaccess line prevents the most common phishing-kit deployment pattern we see.
A03 — Injection (#4)
SQL injection is the canonical example, still widely exploited because homemade contact forms and search pages keep getting written without parameterisation.
// UNSAFE — SQL injection
$user = $db->query("SELECT * FROM users WHERE id = $id");
// SAFE — parameterised
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();// UNSAFE
const user = await db.query(`SELECT * FROM users WHERE id = '${id}'`);
// SAFE — parameterised
const user = await db.query('SELECT * FROM users WHERE id = $1', [id]);NoSQL injection is its own category. With MongoDB:
// UNSAFE — attacker sends {"$ne": null} as password and bypasses auth
db.user.findOne({ email: req.body.email, password: req.body.password });
// SAFE — type check first, look up by email only, verify the hash in code
if (typeof req.body.email !== 'string' || typeof req.body.password !== 'string') {
return res.status(400).send();
}
const user = await db.user.findOne({ email: req.body.email });
const ok = user && await argon2.verify(user.passwordHash, req.body.password);OS command injection — exec'ing user input directly:
// NEVER
exec(`convert ${req.body.input} out.png`);
// Validate first: a plain filename, no path separators, no leading dash
if (!/^[\w][\w.-]*$/.test(req.body.input)) return res.status(400).send();
// Then use execFile with array args (no shell interpretation)
execFile('magick', [req.body.input, 'out.png'], { timeout: 10000 });LDAP injection, XPath injection, NoSQL operator injection — same pattern. Whenever you build a query string that mixes code and data, separate them via parameterised APIs or strict input validation.
For depth on SQL injection specifically, see our SQL injection prevention article.
A05 — Security Misconfiguration (#5)
The most common findings on customer audits:
- Default
admin/adminoradmin/password123left in place after CMS install. /.git/directory exposed to the public web (downloadable with one tool: git-dumper). Anyone gets your full source code, commit history, and any secrets ever committed.phpinfo.phpleft at the document root — leaks PHP version, paths, environment.- Verbose error pages in production — leak stack traces, DB credentials in connection-error messages.
wp-config.phpleft world-readable (644) — on a server without per-account isolation, other users could read it. Use600or640where your setup allows, and never leave copies such aswp-config.php.bakin a public folder.
Hardening checklist (the first three items are server settings for your own VPS; on shared hosting the server is managed for you, and the .htaccess rules work as they are):
# VPS: remove default database accounts
mysql -u root -p -e "DROP USER 'admin'@'localhost'; FLUSH PRIVILEGES;"
# VPS: hide server tokens (nginx config)
server_tokens off;
# VPS: hide PHP version (php.ini; this setting can't be changed in .user.ini)
expose_php = Off
# Disable directory listing (Apache)
Options -Indexes
# Block .git, .env, .htaccess from being served
<FilesMatch "^\.">
Require all denied
</FilesMatch>Security headers for every response:
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header("Content-Security-Policy: default-src 'self'");
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');Express equivalent:
import helmet from 'helmet';
app.use(helmet()); // sensible defaults; tighten as neededFor Cloudflare-fronted sites, set most of these as Transform Rules → Modify Response Headers; saves you from re-implementing per-app.
A09 — Logging & Monitoring Failures (#6 — but it's the reason you discover everything else late)
What we see in practice. Most customer breaches are noticed by Google (a Search Console security issue for hacked content), by an abuse complaint from a third party, or by the web host when outbound mail floods. Almost never by the customer's own monitoring. The reason: there is no monitoring.
Minimum logging discipline:
- Auth events: login success, login failure, MFA challenge, password change, MFA reset.
- Admin actions: role change, user deletion, settings change, data export.
- Payment events: order created, payment captured, refund initiated, payment failed.
- Security-sensitive API calls: file upload, password reset link generation, SSO callback.
- All 5xx errors with stack trace.
- All 4xx auth errors (failed authorization is more interesting than failed authentication).
Ship to a central system. On a VPS, the standard stack is Loki + Grafana for logs + dashboards; Prometheus for metrics; Alertmanager for paging. Our production observability guide covers setup. On shared hosting you can't run those daemons; a hosted log service such as Better Stack or Papertrail (check their current free tiers) is the budget option.
Alerts that catch the actual incidents:
- 10+ failed logins for a single user in 5 minutes.
- 100+ 5xx errors per minute (sudden break or scraper hammering).
- New file in
/wp-content/uploads/with.phpor.phtmlextension. - Outbound email volume more than 2× the rolling-7-day baseline.
- New admin user created (any time).
- Geographic anomaly: login from India, then from a different country within an hour.
The last one — geographic anomaly — catches credential-stuffing breaches faster than anything else.
A02 — Cryptographic Failures (#7)
The bug. Passwords stored as MD5 or SHA-1, secrets committed to git, plain HTTP login forms (or HTTPS with mixed content from HTTP submission endpoints), self-signed cert in production.
Defences:
- Password hashing. Argon2id is the modern default; bcrypt is acceptable. Never SHA-256 / MD5 / SHA-1. Never roll your own with salt+pepper+iterations — the libraries handle this correctly; you won't.
php $hash = password_hash($plain, PASSWORD_ARGON2ID); $ok = password_verify($plain, $hash);javascript import argon2 from 'argon2'; const hash = await argon2.hash(plain, { type: argon2.argon2id }); const ok = await argon2.verify(hash, plain); - HTTPS everywhere. Free SSL (AutoSSL or Let's Encrypt) is included with almost all Domain India hosting. On a VPS you set it up yourself, for example with certbot or Caddy. Add HSTS with
max-age=31536000once you've confirmed all subdomains have certs (HSTS is a one-way commitment for a year). - Encrypt sensitive columns. PII, financial data and other personal data you hold under India's Digital Personal Data Protection (DPDP) Act 2023 — encrypt sensitive fields at rest, for example with AES-256-GCM. (General information, not legal advice.) Keys live in environment variables or a secret manager (AWS Secrets Manager, HashiCorp Vault, Doppler), never in code.
- Secrets management. Never commit
.env, never paste credentials in Slack, never email passwords. Rotate immediately if they leak (and assume they leaked the moment they were anywhere outside a vault). For Indian businesses processing payments, your Razorpay/Stripe webhook secrets and key IDs are the highest-priority items to lock down.
A04 — Insecure Design (#8)
These are the bugs no amount of careful coding fixes — they're in the spec.
- Password reset that emails the current password. This means you stored it in plain text. Never do this.
- OTP for login that's 4 digits with no rate limit. 10,000 possibilities, brute-forced in 5 minutes. Use 6 digits + per-attempt rate limit + per-OTP attempt count.
- "Forgot email" that confirms which emails are registered. Helps account-enumeration. Always reply with the same generic "if an account exists, we sent an email."
- Client-side-only validation. Anyone can disable JavaScript or call your API directly. Always validate on the server.
- Trusting a referrer header for security. Referrers can be set or omitted at will by the client.
- OTP sent over SMS for high-value actions. SIM-swap attacks are common in India; for actions like withdrawals, OTP-via-SMS isn't sufficient — pair with TOTP or push-app confirmation.
Defence: every new feature gets a "what if the attacker did X?" review before code. Threat-model your authentication flow, your money flow, your file-upload flow, and your privilege-elevation flow. Tools like Burp Suite Community Edition (free) help you replay requests with modified inputs.
A10 — Server-Side Request Forgery (SSRF) (#9)
The bug. Your app has a feature that fetches a URL on behalf of the user — image preview, OG-tag scraper, webhook tester. Attacker supplies an internal URL (http://169.254.169.254/latest/meta-data/ for AWS metadata, http://127.0.0.1:6379 for Redis, http://internal-db:5432).
Defence:
import dns from 'node:dns/promises';
import ipaddr from 'ipaddr.js';
// Allow only public unicast addresses: this rejects private (10/8, 172.16/12,
// 192.168/16), loopback, link-local (169.254/16, cloud metadata),
// unspecified, unique-local IPv6 and IPv4-mapped tricks.
function isBlocked(ip) {
return ipaddr.process(ip).range() !== 'unicast';
}
async function safeFetch(userUrl) {
const url = new URL(userUrl);
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('Only HTTP/HTTPS allowed');
}
if (url.hostname === 'localhost' || url.hostname === '0.0.0.0') {
throw new Error('Blocked hostname');
}
// Resolve hostname → every IPv4/IPv6 address, check each one
const ips = (await dns.lookup(url.hostname, { all: true })).map(a => a.address);
if (ips.length === 0 || ips.some(ip => isBlocked(ip))) {
throw new Error('Blocked IP');
}
return fetch(url, { redirect: 'manual' }); // don't follow redirects without re-checking
}The redirect check matters: an attacker can supply https://evil.com/redirect that 302s to http://169.254.169.254/.... You must re-validate on each hop, or simply set redirect: 'manual' and refuse anything that isn't a 200.
DNS rebinding: an attacker's DNS can return a public IP to your check and a private one a moment later when fetch resolves again. For high-risk features, connect to the IP you checked (for example with a custom HTTP agent), or send outbound fetches through a proxy that enforces the same rules.
A08 — Software and Data Integrity Failures (#10)
The bug. Auto-updates without signature verification; CDN scripts without Subresource Integrity; CI pipelines that trust unauthenticated artefacts.
Defences:
- Pin and lock dependencies.
package-lock.json,composer.lock,pnpm-lock.yaml,bun.lock. Don't commit a project without lockfiles. - Verify Docker image signatures (Sigstore / Cosign for OCI registries).
- Sign your own releases — Sigstore for OSS, GPG for traditional distribution.
- Subresource Integrity on CDN scripts — tells the browser to refuse the file if the hash doesn't match: If
lib.jsever changes (intentionally or via supply-chain attack), the browser blocks it. Generate hashes viacat lib.js | openssl dgst -sha384 -binary | openssl base64 -A.html <script src="https://cdn.example.com/lib.js" integrity="sha384-abc123..." crossorigin="anonymous"></script>
What Indian web apps get hit by — common patterns
Generic OWASP examples don't capture the specific attack vectors targeting Indian sites. Common patterns:
- UPI-themed phishing pages cloning PhonePe, Paytm, GPay login screens — deployed on hijacked WordPress sites to steal UPI PINs.
- Aadhaar verification scams — fake pages claiming to "update Aadhaar to avoid suspension" that capture personal details. Deployed on compromised .in sites because the TLD lends credibility.
- GST-refund scam pages — fake CBIC/GST portal clones for tax-season fraud.
- Indian-bank phishing kits — HDFC, ICICI, SBI, Axis login clones; kits rotate depending on which bank is in the news or running a campaign.
- Casino-spam SEO injection — Hindi-language Bollywood/cricket-betting-themed spam pages injected into legitimate sites for SEO-jacking. Often deployed via an outdated WordPress plugin and persists for months until manual SEO audit.
- Outbound spam relay abuse — once a phishing kit lands, the attacker often uses your account to send phishing to new victims. This is the abuse pattern that gets a server IP listed on spam blocklists such as Spamhaus.
Defence is the same as everything above — but the operational cost of these in particular is high because banks, network providers and blocklists such as Spamhaus all run abuse-reporting pipelines, and a single compromised account can trigger reports from all of them.
Tools we recommend
Free, run yourself:
- OWASP ZAP — automated web app scanner. Run weekly against staging.
- Burp Suite Community Edition — manual request inspection and replay. Indispensable for finding IDOR and access-control bugs.
- nikto — web server scanner; finds default files, common misconfigurations.
sqlmap— SQL injection tester. Only run on apps you own.trivy— container vulnerability scanner.- Plugin Check 2.0 (for WordPress plugins you build) — covered in our WordPress plugin guide.
Commercial:
- Snyk, Semgrep, SonarQube — SAST in CI. Free tiers usually enough for a small team.
- Detectify, Pentera, Astra — DAST and attack simulation. Worth it once your app is generating revenue.
- Cloudflare Pro / Business — WAF + bot protection. Blocks much of the automated noise at the edge.
Imunify360 runs on our cPanel and DirectAdmin servers (Webuzo has ImunifyAV) and scans for known malware. It is not a replacement for the rest of this list, but it is a useful baseline.
What to do if your site has been hacked
Suspected compromise — phishing pages, casino spam in Google results, outbound mail floods, abuse reports — work in this order:
- Take the site offline. Cleanest is
mv public_html public_html_offlineso the attacker's payload can't propagate while you investigate. Customers panic about downtime; explain that an extra hour of downtime is much better than another week of phishing damage. - Rotate every credential. cPanel password, FTP, email accounts, WordPress admin, database password, API keys committed to your code, payment gateway keys if exposed.
- Pull a backup from before the compromise. On cPanel and DirectAdmin, JetBackup takes a backup every week and keeps five copies; check which dates are available and restore from the panel (see backup and restore with JetBackup). On Webuzo, backups are taken nightly and restored by support: open a ticket. A full restore from a clean date is usually safer than file-by-file cleanup.
- Audit
wp-content/uploads/,wp-content/plugins/,wp-content/themes/for unexpected.phpfiles. Compare file dates to your last legitimate update. Anything out of place is the malware. - Update everything before going back online. WP core, every plugin, every theme. Remove anything you're not actively using. Same for the underlying PHP version if it's been EOL.
- Tell us. Open a support ticket with your domain name. Support can check the server-side logs for the attacker's IPs and timing. Cleaning the site itself is your job or your developer's; it isn't part of the hosting plan. Our hacked-website checklist walks through the clean-up.
- Request a review in Google Search Console if Google flagged the site for hacked content or malware.
- Write a one-page postmortem for yourself. Which plugin/credential/upload was the entry? How long was the attacker on the system? What did they exfiltrate? You'll do better next time if you actually look back.
Frequently asked questions
Do I need a security team if I'm a one-person business?
No. One engineer who runs npm audit / wp plugin update --all weekly, uses MFA on every admin account, and has a backup strategy is far safer than a typical small website. Hire dedicated security people somewhere around 50 employees or when you start handling regulated data (health, financial, government).
What's "defense in depth"?
Multiple layers of protection. WAF blocks generic bot attacks at the edge; framework-level input validation catches what makes it through; output escaping prevents stored XSS from rendering; logging detects what slips past everything else. No single layer is enough.
Is Cloudflare WAF enough?
Cloudflare WAF blocks most automated attacks — bots scanning for known WordPress CVEs, OWASP-rule-matched payloads, geographic blocking. It does not block targeted attacks where an attacker who knows your stack writes a custom payload. Pair with the application-level defences in this guide.
How often should I update dependencies?
Critical security patches: within 24 hours of disclosure. Regular updates: weekly in dev, monthly batch into prod with smoke testing. For WordPress sites: enable auto-updates for minor versions and trusted plugins; manually review major versions.
ISO 27001 / SOC 2 — do I need them?
Selling to enterprises or government? Yes, increasingly. Serving small businesses or consumers only? Informal but disciplined security hygiene is enough. Don't pay for compliance certs you don't have a buyer asking for.
My WordPress site got hacked twice — same hosting, different cleanup teams. Why?
Almost always because the entry vector was never identified. Cleanup teams remove the malware they find but don't determine how it got there. Without that, the attacker's webshell or backdoor account remains, and the site is recompromised within weeks. Fixing requires forensics — server-side logs from your host, Wordfence/Sucuri scan reports, and a full audit of plugin versions at the time of compromise. Open a ticket; we'll provide what we can from the server-side.
Should I use a managed WordPress security service like Wordfence Premium / Sucuri / MalCare?
They're worth it for sites that have already been compromised once or that handle high-value transactions. For a brochure site that's kept up to date, the basics in this guide plus the server-side malware scanning on our shared hosting (Imunify360 on cPanel and DirectAdmin, ImunifyAV on Webuzo) are usually sufficient.
We use AWS / Azure / GCP — does any of this change?
Cloud providers add their own security primitives (IAM, KMS, GuardDuty, security hub) that are powerful but easy to misconfigure. Cloud-specific OWASP analogues exist (AWS Well-Architected security pillar, Azure Cloud Adoption Framework). Apply both: the application-level defences here, plus your cloud provider's IAM/network discipline.
I'm building a fintech / health / government app. What changes?
Compliance overhead — RBI guidelines for fintech (data localisation, audit trails), DPDPA + sector rules for health, MEITY-empanelled hosting and audit for government. Out of scope for this article but a real consideration. Talk to a compliance consultant before you build.
Bottom line
Most attacks on Indian websites aren't sophisticated. They exploit out-of-date software, weak passwords, and unrestricted file uploads — three categories that you can shut down in a weekend. Do those before anything else, set up basic logging so you'll know if something gets past, and put MFA on every admin account that supports it. The rest of the OWASP Top 10 matters too, but if you've nailed the top three, you've taken yourself out of the easy-target pool that most automated attacks aim at.
If your site has been compromised, open a support ticket with your domain name; support can check the server-side logs for accounts hosted with us.
A self-managed Domain India VPS gives you full root access and full control of your logs, firewall and security tooling, from ₹553 a month excluding GST.
See VPS plans