











现在想找一个免费、稳定的魔法玩具太难了,动辄各种错误、IP 污染,根本没法好好用。直到有天刷洋抖无意间看到 Alwaysdata。免费版给 1/4 核心和 128M 内存,跑不了什么大东西,但挂几个链接够用了。先看效果:

看个 4K 没问题。
你需要一个干净的住宅 IP,风控值别太高。风控高的话注册时会让你绑卡。怎么换 IP 自己想办法,搜一下 proxy 相关工具就行。如果注册时弹了绑卡提示,换个 IP 重新来。我换了几个才成功。没弹绑卡提示的话,恭喜你,继续下一步。
登录后台,点 Sites > Sites,你会看到自己的地址 xxxx.alwaysdata.net,这就是后面要用的访问地址。
点右侧 Modify > Configuration,把运行时从 PHP 改成 Node.js,Command 栏填 npm start,保存。
然后进 Advanced > Servers,点右上角 Add a service,Command 和 Monitoring command 都填 npm start,勾上 Paused。这样服务停止时也能自动保活,这步很重要。
在任意位置新建一个文件夹,创建以下文件:
打开你的 AI 工具,让它帮你写一个炫酷的欧美行业首页,保存为 index.html。
index.js 的内容:
const os = require('os');
const http = require('http');
const fs = require('fs');
const axios = require('axios');
const net = require('net');
const path = require('path');
const crypto = require('crypto');
const { Buffer } = require('buffer');
const { exec, execSync } = require('child_process');
const { pipeline } = require('stream');
const { WebSocket, createWebSocketStream } = require('ws');
const UUID = process.env.UUID || '8baa4f2b-9835-4747-82a1-560ebf25aabb';
const NEZHA_SERVER = process.env.NEZHA_SERVER || '';
const NEZHA_PORT = process.env.NEZHA_PORT || '';
const NEZHA_KEY = process.env.NEZHA_KEY || '';
const DOMAIN = process.env.DOMAIN || 'xxxx.alwaysdata.net';
const AUTO_ACCESS = process.env.AUTO_ACCESS !== 'false';
const WSPATH = process.env.WSPATH || UUID.slice(0, 8);
const SUB_PATH = process.env.SUB_PATH || 'xxxx';
const NAME = process.env.NAME || 'xxxx';
const PORT = process.env.PORT || 8403;
let ISP = '';
const GetISP = async () => {
try {
const res = await axios.get('https://api.ip.sb/geoip');
const data = res.data;
ISP = `${data.country_code}-${data.isp}`.replace(/ /g, '_');
} catch (e) {
ISP = 'Unknown';
}
}
const ispPromise = GetISP();
const httpServer = http.createServer(async (req, res) => {
if (req.url === '/') {
const filePath = path.join(__dirname, 'index.html');
fs.readFile(filePath, 'utf8', (err, content) => {
if (err) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('Hello world!');
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(content);
});
return;
} else if (req.url === `/${SUB_PATH}`) {
await ispPromise;
const namePart = NAME ? `${NAME}-${ISP}` : ISP;
const vlessURL = `vless://${UUID}@${DOMAIN}:443?encryption=none&security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`;
const trojanURL = `trojan://${UUID}@${DOMAIN}:443?security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`;
const subscription = vlessURL + '\n' + trojanURL;
const base64Content = Buffer.from(subscription).toString('base64');
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(base64Content + '\n');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found\n');
}
});
const wss = new WebSocket.Server({ server: httpServer });
const uuid = UUID.replace(/-/g, "");
const uuidBuffer = Buffer.from(uuid, 'hex');
const trojanHash = crypto.createHash('sha224').update(UUID).digest('hex');
const dnsCache = new Map();
const DNS_CACHE_TTL = 300000;
const DNS_CACHE_MAX = 1000;
const DOH_ENDPOINTS = [
'https://dns.google/resolve',
'https://cloudflare-dns.com/dns-query',
];
function resolveHost(host) {
return new Promise((resolve, reject) => {
if (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(host)) {
resolve(host);
return;
}
const cached = dnsCache.get(host);
if (cached && Date.now() - cached.time < DNS_CACHE_TTL) {
resolve(cached.ip);
return;
}
let attempts = 0;
function tryNextDNS() {
if (attempts >= DOH_ENDPOINTS.length) {
reject(new Error(`Failed to resolve ${host} with all DNS servers`));
return;
}
const dohUrl = DOH_ENDPOINTS[attempts];
attempts++;
const dnsQuery = `${dohUrl}?name=${encodeURIComponent(host)}&type=A`;
axios.get(dnsQuery, {
timeout: 5000,
headers: { 'Accept': 'application/dns-json' }
})
.then(response => {
const data = response.data;
if (data.Status === 0 && data.Answer && data.Answer.length > 0) {
const ip = data.Answer.find(record => record.type === 1);
if (ip) {
if (dnsCache.size >= DNS_CACHE_MAX) {
const now = Date.now();
for (const [key, val] of dnsCache) {
if (now - val.time >= DNS_CACHE_TTL) dnsCache.delete(key);
}
if (dnsCache.size >= DNS_CACHE_MAX) {
const firstKey = dnsCache.keys().next().value;
dnsCache.delete(firstKey);
}
}
dnsCache.set(host, { ip: ip.data, time: Date.now() });
resolve(ip.data);
return;
}
}
tryNextDNS();
})
.catch(error => {
tryNextDNS();
});
}
tryNextDNS();
});
}
function connectToTarget(ws, host, port, initialData) {
const duplex = createWebSocketStream(ws);
const doConnect = (targetHost) => {
net.connect({ host: targetHost, port }, function() {
if (initialData && initialData.length > 0) {
this.write(initialData);
}
duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex);
}).on('error', () => {});
};
resolveHost(host).then(doConnect).catch(() => doConnect(host));
}
function handleVlessConnection(ws, msg) {
const [VERSION] = msg;
const id = msg.slice(1, 17);
if (Buffer.compare(id, uuidBuffer) !== 0) return false;
let i = msg.slice(17, 18).readUInt8() + 19;
const port = msg.slice(i, i += 2).readUInt16BE(0);
const ATYP = msg.slice(i, i += 1).readUInt8();
const host = ATYP == 1 ? msg.slice(i, i += 4).join('.') :
(ATYP == 2 ? new TextDecoder().decode(msg.slice(i + 1, i += 1 + msg.slice(i, i + 1).readUInt8())) :
(ATYP == 3 ? msg.slice(i, i += 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []).map(b => b.readUInt16BE(0).toString(16)).join(':') : ''));
ws.send(new Uint8Array([VERSION, 0]));
connectToTarget(ws, host, port, msg.slice(i));
return true;
}
function handleTrojanConnection(ws, msg) {
try {
if (msg.length < 58) return false;
const receivedPasswordHash = msg.slice(0, 56).toString();
if (trojanHash !== receivedPasswordHash) return false;
let offset = 56;
if (msg[offset] === 0x0d && msg[offset + 1] === 0x0a) {
offset += 2;
}
const cmd = msg[offset];
if (cmd !== 0x01) return false;
offset += 1;
const atyp = msg[offset];
offset += 1;
let host, port;
if (atyp === 0x01) {
host = msg.slice(offset, offset + 4).join('.');
offset += 4;
} else if (atyp === 0x03) {
const hostLen = msg[offset];
offset += 1;
host = msg.slice(offset, offset + hostLen).toString();
offset += hostLen;
} else if (atyp === 0x04) {
host = msg.slice(offset, offset + 16).reduce((s, b, i, a) =>
(i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), [])
.map(b => b.readUInt16BE(0).toString(16)).join(':');
offset += 16;
} else {
return false;
}
port = msg.readUInt16BE(offset);
offset += 2;
if (offset < msg.length && msg[offset] === 0x0d && msg[offset + 1] === 0x0a) {
offset += 2;
}
connectToTarget(ws, host, port, offset < msg.length ? msg.slice(offset) : null);
return true;
} catch (error) {
return false;
}
}
wss.on('connection', (ws, req) => {
const reqUrl = req.url || '';
if (!reqUrl.includes(`/${WSPATH}`)) {
ws.close(1008, 'Forbidden');
return;
}
const idleTimer = setTimeout(() => ws.close(1000, 'Idle timeout'), 30000);
ws.once('message', msg => {
clearTimeout(idleTimer);
if (msg.length > 17 && msg[0] === 0) {
const id = msg.slice(1, 17);
const isVless = Buffer.compare(id, uuidBuffer) === 0;
if (isVless) {
if (!handleVlessConnection(ws, msg)) {
ws.close();
}
return;
}
}
if (!handleTrojanConnection(ws, msg)) {
ws.close();
}
}).on('error', () => {});
});
const getDownloadUrl = () => {
const arch = os.arch();
if (arch === 'arm' || arch === 'arm64' || arch === 'aarch64') {
if (!NEZHA_PORT) {
return 'https://arm64.ssss.nyc.mn/v1';
} else {
return 'https://arm64.ssss.nyc.mn/agent';
}
} else {
if (!NEZHA_PORT) {
return 'https://amd64.ssss.nyc.mn/v1';
} else {
return 'https://amd64.ssss.nyc.mn/agent';
}
}
};
const downloadFile = async () => {
if (!NEZHA_SERVER && !NEZHA_KEY) return;
try {
const url = getDownloadUrl();
const response = await axios({
method: 'get',
url: url,
responseType: 'stream'
});
const writer = fs.createWriteStream('npm');
return new Promise((resolve, reject) => {
pipeline(response.data, writer, (err) => {
if (err) { reject(err); return; }
console.log('npm download successfully');
exec('chmod +x npm', (err) => {
if (err) reject(err);
else resolve();
});
});
});
} catch (err) {
throw err;
}
};
const runnz = async () => {
try {
const status = execSync('ps aux | grep -v "grep" | grep "./[n]pm"', { encoding: 'utf-8' });
if (status.trim() !== '') {
console.log('npm is already running, skip running...');
return;
}
} catch (e) {}
await downloadFile();
let command = '';
let tlsPorts = ['443', '8443', '2096', '2087', '2083', '2053'];
if (NEZHA_SERVER && NEZHA_PORT && NEZHA_KEY) {
const NEZHA_TLS = tlsPorts.includes(NEZHA_PORT) ? '--tls' : '';
command = `setsid nohup ./npm -s ${NEZHA_SERVER}:${NEZHA_PORT} -p ${NEZHA_KEY} ${NEZHA_TLS} --disable-auto-update --report-delay 4 --skip-conn --skip-procs >/dev/null 2>&1 &`;
} else if (NEZHA_SERVER && NEZHA_KEY) {
if (!NEZHA_PORT) {
const port = NEZHA_SERVER.includes(':') ? NEZHA_SERVER.split(':').pop() : '';
const NZ_TLS = tlsPorts.includes(port) ? 'true' : 'false';
const configYaml = `client_secret: ${NEZHA_KEY}
debug: false
disable_auto_update: true
disable_command_execute: false
disable_force_update: true
disable_nat: false
disable_send_query: false
gpu: false
insecure_tls: true
ip_report_period: 1800
report_delay: 4
server: ${NEZHA_SERVER}
skip_connection_count: true
skip_procs_count: true
temperature: false
tls: ${NZ_TLS}
use_gitee_to_upgrade: false
use_ipv6_country_code: false
uuid: ${UUID}`;
fs.writeFileSync('config.yaml', configYaml);
}
command = `setsid nohup ./npm -c config.yaml >/dev/null 2>&1 &`;
} else {
console.log('NEZHA variable is empty, skip running');
return;
}
try {
exec(command, { shell: '/bin/bash' }, (err) => {
if (err) console.error('npm running error:', err);
else console.log('npm is running');
});
} catch (error) {
console.error(`error: ${error}`);
}
};
async function addAccessTask() {
if (!AUTO_ACCESS) return;
if (!DOMAIN) return;
const fullURL = `https://${DOMAIN}`;
try {
const res = await axios.post("https://oooo.serv00.net/add-url", {
url: fullURL
}, {
headers: { 'Content-Type': 'application/json' }
});
console.log('Automatic Access Task added successfully');
} catch (error) {}
}
const delFiles = () => {
fs.unlink('npm', () => {});
fs.unlink('config.yaml', () => {});
};
httpServer.listen(PORT, () => {
runnz().catch(err => console.error('runnz error:', err.message));
setTimeout(() => {
delFiles();
}, 180000);
addAccessTask();
console.log(`Server is running on port ${PORT}`);
}); package.json:
{
"name": "nodews",
"version": "1.0.0",
"description": "Nodejs-server",
"main": "index.js",
"private": false,
"scripts": {
"start": "node index.js"
},
"dependencies": {
"ws": "^8.14.2",
"axios": "^1.12.2"
},
"engines": {
"node": ">=14"
}
} 把以上代码分别保存为 index.js 和 package.json。代码来自大佬的项目,我做了一些优化。
用编辑器打开 index.js,改几个地方:
const UUID = process.env.UUID || '8baa4f2b-9835-4747-82a1-560ebf25aabb'
去 UUID 生成网站随便生成一个替换进去。
const DOMAIN = process.env.DOMAIN || 'xxxx.alwaysdata.net'
改成你刚才拿到的地址。
const SUB_PATH = process.env.SUB_PATH || 'xxxx';
订阅路径,比如访问 xxxx.alwaysdata.net/xxxx 就能拿到订阅。
const NAME = process.env.NAME || 'xxxx';
节点名称,随便填。
const PORT = process.env.PORT || 8403;
端口号,alwaysdata 要求在 8300-8499 之间,随便挑一个。
改完之后,到这里混淆一下代码,保存。
在文件夹地址栏输入 cmd,执行上传:
scp index.html index.js package.json xxxx@xxxx.alwaysdata.net:~ 输入创建机器时设的SSH密码,上传成功后用然后用 ssh -p 22 xxxx@xxxx.alwaysdata.net输入npm i && npm start之后访问xxxx.alwaysdata.net/xxxx就能拿到订阅了。
登录 Cloudflare 后台,新建一个 Worker,粘贴以下代码:
/**
* Cloudflare Workers - 节点反代 CDN 加速
*
* 功能:
* - 反向代理 HTTP 请求(订阅地址等)
* - 自动代理 WebSocket 连接(VLESS / Trojan)
* - 支持多后端域名随机负载均衡
* - 自动故障转移(某个后端失败时尝试下一个)
* - 通过环境变量动态配置,无需改代码
*
* 部署方式:
* 1. Cloudflare Dashboard → Workers & Pages → 创建 Worker → 粘贴此代码
* 2. 或使用 wrangler: npx wrangler deploy
*
* 使用方式:
* - 将客户端的节点地址 / 订阅地址域名改为 Worker 的域名即可
* - 在 Worker 设置中添加环境变量 BACKEND_DOMAINS 可动态配置后端
*/
const DEFAULT_BACKENDS = [
'xxxx.alwaysdata.net',
];
function getRandomItem(array) {
return array[Math.floor(Math.random() * array.length)];
}
function getBackends(env) {
if (env.BACKEND_DOMAINS) {
const list = env.BACKEND_DOMAINS.split(',')
.map(d => d.trim())
.filter(Boolean);
if (list.length > 0) return list;
}
return DEFAULT_BACKENDS;
}
export default {
async fetch(request, env, ctx) {
try {
const url = new URL(request.url);
const backends = getBackends(env);
const shuffled = [...backends].sort(() => Math.random() - 0.5);
let lastError = null;
for (const backend of shuffled) {
try {
const backendUrl = new URL(url.pathname + url.search, `https://${backend}`);
const proxyRequest = new Request(backendUrl, request);
proxyRequest.headers.set('Host', backend);
const clientIP = request.headers.get('CF-Connecting-IP');
if (clientIP) {
proxyRequest.headers.set('X-Real-IP', clientIP);
proxyRequest.headers.set('X-Forwarded-For', clientIP);
}
const response = await fetch(proxyRequest);
if (response.status >= 500 && shuffled.length > 1) {
lastError = new Error(`Backend ${backend} returned ${response.status}`);
continue;
}
const newResponse = new Response(response.body, response);
newResponse.headers.delete('X-Powered-By');
newResponse.headers.delete('Server');
newResponse.headers.delete('Via');
newResponse.headers.set('X-CDN-Proxy', 'Cloudflare');
return newResponse;
} catch (err) {
lastError = err;
continue;
}
}
return new Response('All backends unavailable', {
status: 503,
headers: { 'Content-Type': 'text/plain' },
});
} catch (error) {
return new Response('Internal Server Error', {
status: 500,
headers: { 'Content-Type': 'text/plain' },
});
}
},
}; 把 DEFAULT_BACKENDS 里的地址换成你自己的就行。
以上就是全部折腾过程。该怎么玩全看你自己发挥了!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。